你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 模仿斗魚的部分界面介紹一(部分使用RxSwift, MVVM)

模仿斗魚的部分界面介紹一(部分使用RxSwift, MVVM)

編輯:IOS開發基礎

授權轉載,作者:ZeroJ

前言:

之前閒著的時候就隨便模仿斗魚的界面寫了一些界面, 最初的時候在網上找到的獲取直播的sign加密方式還是可用的, 當時還使用IJKMediaFramework, 集成了直播視頻的獲取和播放, 當時的項目也就還是挺龐大的, 不過大約在7.21 左右斗魚的api升級了, 然後就不能獲取到直播了, 所以現在把項目中的直播相關的全部都刪除了。

目前項目中就只能看到部分的界面和一些網絡的請求了, 項目是使用swift來實現的, 但是如果你是最初接觸swift的話, 有一些地方可能可以參考一下. 項目地址

一些頁面的效果如下

1271831-7fdd37d24f4da713.gif

1271831-3895496bd94608de.gif

1271831-c191a51cff54ccbf.gif

1271831-c191a51cff54ccbf.gif

1271831-c191a51cff54ccbf.gif

1271831-356c8d10bd6902ae.gif

1271831-65c82e3d17b3417d.gif

關於項目的一些解釋

一. 最初是使用MVC來設計的項目的, 最近開始接觸MVVM設計模式,在網上找到的各種MVVM的相關的資料, 就把先前的這個項目拿來改動試試, 然後在改的時候發現, 很多時候不可能做到理想的MVVM架構的, 因為可能使用到第三方的東西導致不能很方便的使用MVVM, 另外就是, 個人覺得簡單的界面使用MVVM就是在浪費時間

這裡關於MVVM就簡單的提一下了

MVVM = model, view(viewController), viewModel

在MVVM中, 每個view(viewController)理論上對應一個viewModel, view(viewController)負責界面的布局, 和響應用戶的點擊, 以及展示頁面...

viewModel用於處理view的所有的展示邏輯(請求網絡, 操作數據庫, 格式化字符串...), 而且完美的viewModel裡面是不應該引入UIKit的, 所以viewModel就擁有view所需要的所有的數據, viewModel中只進行數據的加工, 能夠對這些數據進行必要的操作, 然後讓對應的view更新數據.

因為view是擁有viewModel的, 所以要實現view和viewModel的通信(view更新的時候同步更新viewModel中的數據)很簡單, 但是要實現viewModel和view的綁定就很難得, 有時候你可以選擇(kvo, 代理, 通知, block...), 但是很多時候實現都是非常的麻煩的, 因為你需要做到在viewModel中更新的時候同步更新對應的view的狀態.

所以這個時候你就需要一個響應式編程的框架,來實現view和viewModel的(單)雙向綁定, 比如OC中你可以用ReactiveCocoa, 在swift中, 你可以使用ReactiveCocoa, RxSwift, Bond...(推薦RxSwift, 號稱是符合RX官方的設計, 跨平台的設計理念, RxJava, RxJS...可以類似的使用)

另外有人提出更符合MVVM的是viewModel只暴露一些輸入和輸出信號給view, 通過將這些信號綁定到view上面實現和view的同步更新, 而viewModel不暴露方法給view, 比如按鈕的點擊和viewModel的一個按鈕點擊的信號綁定, 在viewModel中通過訂閱這個信號處理按鈕的點擊, 而不是在view中調用viewModel的響應按鈕點擊的方法... 不過個人更傾向於暴露方法, 因為感覺使用信號的話對第三方的框架依賴太大了

model和MVC中的model基本相似的角色, 這裡就不介紹了, 關於MVVM的更多的介紹, 推薦看這一系列的博客

二. 項目最初是集成了IJKMediaFramework並且實現了直播的一些功能, 不過由於斗魚Api的變動, 就全部給移除了

1470654414865041.png

三. 項目使用純swift寫的, 所以很多的第三方的依賴就選擇了使用swift的版本的, 比如字典和模型的互轉沒有使用Mantle了, 取而代之的是使用了ObjectMapper, ObjectMapper的開發者為了更符合swift風格的編程, 沒有在基於OC的運行時來實現了, 因為使用OC的運行時只能獲取到繼承自NSObject的class的屬性的類型和值, 不能夠獲取到純swift的class, struct, enum等的屬性的類型和值了, 因為目前大家使用swift的時候更喜歡用struct來作為model, 所以基於運行時就不現實了, 不過帶來的一點不方便就是: 需要手動的建立映射關系(這也有一個好處, 可以多個key映射json的同一個key), 當然隨著swift的進步, 他的Reflect功能增強的話就可以方便的實現自動映射(雖然現在也可以實現, 不過不被推薦)

1271831-2823b2f2e7b6d4cd.png

不過在使用上也是很簡單的, 只需要這樣, 如下調用這個map就將服務器返回的resultJson轉換為了TagModel模型了

1470654441647935.png

四. 網絡請求的方面沒有使用AFNetworking了, 而是使用出自同一個作者的Alamofire, 使用也是更加的簡單和方便, 作者利用swift的優勢使得Alamofire能讓開發者更方便的實現各種需要的自定義配置

這裡我只是簡單的使用了GET和POST請求

/// get
    class func GET(URLString: String, parameters: [String: AnyObject]? = nil, successHandler:((result: AnyObject?) -> Void)?, failureHandler: ((error: NSError?) -> Void)?) {
        Alamofire.request(.GET, URLString, parameters: parameters, encoding: .URL, headers: nil).responseJSON { (response) in
            if response.result.isSuccess {
                print("初始請求:\(response.request)")
                successHandler?(result: response.result.value)
            } else {
                failureHandler?(error: response.result.error)
            }
        }
    }
    /// post
    class func POST(URLString: String, parameters: [String: AnyObject]? = nil, successHandler:((result: AnyObject?) -> Void)?, failureHandler: ((error: NSError?) -> Void)?) {
        Alamofire.request(.POST, URLString, parameters: parameters, encoding: .URL, headers: nil).responseJSON { (response) in
            if response.result.isSuccess {
                successHandler?(result: response.result.value)
            } else {
                failureHandler?(error: response.result.error)
            }
        }
    }}

如你所見, 使用就是如下的這麼簡單

1470654470927387.png

五. 圖片的加載方面沒有使用SDWebimage, 而是使用了王巍的Kingfisher, 其中的接口設計以及原理和SDWebimage相類似, 所以你可以很快的就上手Kingfisher的使用了

/// 使用分類來加載圖片, 同時提供進度和加載完成後的handler, 在這個handler裡可以處理請求完成的圖片
imageView.kf_setImageWithURL(NSURL(string: data.room_src)!, placeholderImage: nil, optionsInfo: nil, progressBlock: nil, completionHandler: nil)
/// 先下載載設置圖片
KingfisherManager.sharedManager.retrieveImageWithURL(NSURL(string: data.room_src)!, optionsInfo: nil, progressBlock: nil) {[weak self] (image, error, cacheType, imageURL) in
      guard let validSelf = self where image != nil else {
          return
      }
      validSelf.imageView.zj_setCircleImage(image, radius: 20.0)
 }

六. 自動布局上面沒有使用masonry, 而是使用了同一個團隊開發的SnapKit, 所以使用的方法幾乎一樣, 不過因為swift更適合函數式編程, 所以語法看上去也是自然了許多

1271831-06b599d6082970b5.png

七.關於RxSwift, 如果要使用MVVM的設計模式的話, 必須得解決view和viewModel的綁定問題, 那麼最方便的就是使用第三方的響應式編程的框架, 這裡推薦使用RxSwift, 這個學習的路線確實是很陡峭, 不是很容易就掌握了, 所以在項目中, 我只是在RecommendController簡單的示例了一下RxSwift的使用, 另外RxSwift不單是方便MVVM, 更重要的是, 他把所有的(kvo, delegate, action- target, block, notification...)統一為了一種簡單的使用方式, 真正的實現了高聚合, 低耦合. 同時RxSwift裡面還有很多的用處, 比如實現搜索需求的時候, 需要在用戶輸入後實時的請求服務器, 這個時候, 就可以使用RxSwift和簡單的實現, 在用戶輸入停留一段時間後請求服務器, 同時當輸入的內容不變的時候不請求服務器... 總之很多的方便的功能, 絕對超乎你的想象, 等待你去發現...

1271831-bc68dce11ec6203b.png

1470654531189062.png

八. 關於項目中文件的說明

main文件夾下主要是項目中通用的一些東西

1271831-7b81c074da703c66.png

MainNavigationController主要是用來統一配置項目中所有的Navigationtroller的一些屬性, 比如在這個項目中, 我只是統一開啟了全屏滑動返回的功能, 和攔截了彈出新控制器的方法, 你需要的各種其他自定義的, 建議也集中放在這裡

class MainNavigationController: UINavigationController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 開啟全屏pop手勢
        zj_enableFullScreenPop(true)
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
    // 攔截 統一處理
    override func showViewController(vc: UIViewController, sender: AnyObject?) {
        vc.hidesBottomBarWhenPushed = true
        super.showViewController(vc, sender: sender)
    }
}

MainTabBarController 是用來統一處理項目中的Tabbarcontroller的一些屬性, 當然很多人都是直接放在Appdelegate中來設置的, 個人還是喜歡全部分離開來

override func viewDidLoad() {
      super.viewDidLoad()
      /// 設置子控制器
      setupChildVcs()
      /// 設置item的字體顏色
      setTabBarItemColor()
  }
  func setTabBarItemColor() {
      UITabBarItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.orangeColor()], forState: .Selected)
      UITabBarItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.lightGrayColor()], forState: .Normal)
  }
  func setupChildVcs() {
      let homeVc = addChildVc(HomeController(), title: "首頁", imageName: "btn_home_normal_24x24_", selectedImageName: "btn_home_selected_24x24_")
      let liveVc = addChildVc(LiveColumnController(), title: "直播", imageName: "btn_column_normal_24x24_", selectedImageName: "btn_column_selected_24x24_")
      let concernVc = addChildVc(ConcernController(), title: "關注", imageName: "btn_live_normal_30x24_", selectedImageName: "btn_live_selected_30x24_")
      let profileVc = addChildVc(ProfileController(), title: "我的", imageName: "btn_user_normal_24x24_", selectedImageName: "btn_user_selected_24x24_")
      viewControllers = [homeVc, liveVc, concernVc, profileVc]
  }
  func addChildVc(childVc: UIViewController, title: String, imageName: String, selectedImageName: String) -> UINavigationController {
      let navi = MainNavigationController(rootViewController: childVc)
      let image = UIImage(named: imageName)?.imageWithRenderingMode(.AlwaysOriginal)
      let selectedImage = UIImage(named: selectedImageName)?.imageWithRenderingMode(.AlwaysOriginal)
      let tabBarItem = UITabBarItem(title: title, image: image, selectedImage: selectedImage)
      navi.tabBarItem = tabBarItem
      return navi
  }

BaseViewController 是用來作為所有控制器的基類, 在裡面統一處理一些設置, 在OC中, 我一般不喜歡使用基類來處理, 都是使用分類 +load()來統一設置一些, 比如設置view.backgroundColor, 但在swift中目前, mock不方便, 所以就使用了基類, 這也是很多朋友都喜歡使用的方式

class BaseViewController: UIViewController {
  /// 用於RxSwift
  var disposeBag = DisposeBag()
  /// 標記是否更新了布局
  private var didUpdateConstraints = false
  override func viewDidLoad() {
      super.viewDidLoad()
      view.backgroundColor = UIColor.whiteColor()
  }
  /// 重寫方法
  override func updateViewConstraints() {
      if !didUpdateConstraints {
          addConstraints()
          didUpdateConstraints = true
      }
      super.updateViewConstraints()
  }
  /// 子類重寫, 用於添加自動布局
  func addConstraints() {
      /// default do nothing
  }
}

lib文件夾下主要是使用的一些封裝好的東西, 不過在這個項目中, lib裡面的全是用的我自己寫的一些東西, 一些之前已經放在了github上了, 這裡簡單介紹一下, 給自己一個廣告??

FullScreenPopNavigationController -> 是為了方便navigationController實現全屏側滑返回的功能的, 如你所見, 打開和關閉都只需一行代碼// zj_enableFullScreenPop(true) (true)開啟全屏pop手勢, false關閉

1271831-43cad0b0e68217a0.png

ZJPullToRefresh -> 是我用swift寫的一個和MJRefresh基本功能和使用相似的上下拉刷新控件

let normalAnimator = NormalAnimator.loadNormalAnimatorFromNib()
normalAnimator.isAutomaticlyHidden = true
normalAnimator.lastRefreshTimeKey = "recommondHeader"
collectionView.zj_addRefreshHeader(normalAnimator) { [weak self] in
 /// 這裡是加載過程
}

1271831-12ec1840860a2839.png

PPTView -> 是一個簡單的圖片輪播, 這個實現沒什麼難度, 可以使用鏈式調用, 幾個鏈式調用的設置和tableView的幾個代理方法的功能類似,在網絡加載完畢的時候調用self.pptView.reloadData()可以像tableview一樣重新加載數據

let pptView = PPTView.PPTViewWithImagesCount {[weak self] in
   guard let `self` = self else { return 0 }
   return self.viewModel.pptData.count
}
.setupImageAndTitle({[weak self] (titleLabel, imageView, index) in
   guard let `self` = self else { return }
//            let model = self.viewModel.pptData.value[index]
   let model = self.viewModel.pptData[index]
   titleLabel.textAlignment = .Left
   titleLabel.text = "    " + "\(model.title)"
   imageView.image = UIImage(named: "2")
   imageView.kf_setImageWithURL(NSURL(string: model.pic_url), placeholderImage: UIImage(named: "1"))
})
.setupPageDidClickAction({[weak self] (clickedIndex) in
   guard let `self` = self else { return }
   let playerVc = PlayerController()
   playerVc.title = "播放"
   playerVc.roomID = String(self.viewModel.pptData[clickedIndex].id)
   self.showViewController(playerVc, sender: nil)
})
pptView.frame = CGRect(x: 0, y: 0, width: Constant.screenWidth, height: ConstantValue.pptViewHeight)
pptView.pageControlPosition = .BottomRight
return pptView

1271831-12ec1840860a2839.png

ScrollPageView -> 是用來實現類似網易新聞的頭部標簽欄等多種效果

1271831-1235150813708900.png

TypedTableView -> 是簡單封裝了一下"靜態"tableView的使用, 這個看個人的習慣

1271831-d67de9e34191b1b8.png

let row1Data = TypedCellDataModel(name: "開播提示", iconName: "1")
let row2Data = TypedCellDataModel(name: "票務查詢", iconName: "1")
let row3Data = TypedCellDataModel(name: "設置選項", iconName: "1")
let row4Data = TypedCellDataModel(name: "手游中心", iconName: "1", detailValue: "玩游戲領魚丸")
let row1 = CellBuilder(dataModel: row1Data, cellDidClickAction: {
     SimpleHUD.showHUD("未實現相關功能", autoHide: true, afterTime: 1.0)
})
let row2 = CellBuilder(dataModel: row2Data, cellDidClickAction: {
     SimpleHUD.showHUD("未實現相關功能", autoHide: true, afterTime: 1.0)
})
let row3 = CellBuilder(dataModel: row3Data, cellDidClickAction: {[unowned self] in
     self.showViewController(SettingController(), sender: nil)
})
let row4 = CellBuilder(dataModel: row4Data, cellHeight: 50, cellDidClickAction: {[unowned self] in
     self.showViewController(TestController(), sender: nil)
})
let section1 = CommonTableSectionData(headerTitle: nil, footerTitle: nil, headerHeight: 10, footerHeight: nil, rows: [row1, row2, row3])
let section2 = CommonTableSectionData(headerTitle: nil, footerTitle: nil, headerHeight: 10, footerHeight: 10, rows: [row4])
data = [section1, section2]

PhotoBrowser -> 圖片浏覽器, 可以支持浏覽本地和網絡的圖片,很方便的簡單的實現類似空間, 朋友圈動態的多張圖片浏覽, 已經寫好各種手勢放大縮小, 保存等常用功能, 本項目中只是簡單的使用了, 浏覽本地的圖片

lazy var profileHeadView: ProfileHeadView =  {
      let profileHeadView = ProfileHeadView.LoadProfileHeadViewFormLib()
      profileHeadView.didTapImageViewHandler = {[weak self] imageView in
          guard let `self` = self else { return }
          /// 彈出圖片浏覽器
          let photoModel = PhotoModel(localImage: imageView.image, sourceImageView: nil)
          let photoBrowser = PhotoBrowser(photoModels: [photoModel])
          photoBrowser.hideToolBar = true
          photoBrowser.show(inVc: self, beginPage: 0)
      }
      return profileHeadView
  }()

UsefulPickerView -> 簡單方便的彈出城市選擇, 日期選擇, 單列, 多列選擇的pickerView,

let row1 = CellBuilder(dataModel: row1Data, cellDidClickAction: {
  UsefulPickerView.showDatePicker(row1Data.name, doneAction: { (selectedDate) in
       EasyHUD.showHUD("提示時間是---\(selectedDate)", autoHide: true, afterTime: 1.0)
    })
})
let row2 = CellBuilder(dataModel: row2Data, cellDidClickAction: {
  UsefulPickerView.showSingleColPicker(row2Data.name, data: ["是", "否"], defaultSelectedIndex: 0, doneAction: { (selectedIndex, selectedValue) in
       EasyHUD.showHUD("選擇了---\(selectedValue)", autoHide: true, afterTime: 1.0)
  })
})

感覺這篇文章已經很長了, 先就介紹到這裡吧, 當然希望你也可以自己下載項目下來看看, 項目地址:https://github.com/jasnig/DouYuTVMutate

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved