前言
一直想利用空余時間寫個開源項目契吉,兜兜轉(zhuǎn)轉(zhuǎn)許久,光說不練都是空把戲诡渴,咱擼起袖子就是干捐晶。說做咱就開始利用空閑時間騷動起來吧。本開源項目講解了一些App常見功能界面的搭建以及實現(xiàn)思路玩徊,適合新手以及正在學(xué)習(xí)Swift的同胞們租悄。
號外
:最近似乎把自己活成了一個網(wǎng)癮少年般的模樣谨究,一到某個點就開始相約雞場恩袱。著實尷尬!=赫堋畔塔!
目錄
一、關(guān)于項目
二鸯屿、效果預(yù)覽
三澈吨、詳細講解
3.0 歡迎模塊分析
3.1 首頁模塊分析
3.2 我聽模塊分析
3.3 播放模塊分析
3.4 發(fā)現(xiàn)模塊分析
3.5 我的模塊分析
四、遇到的問題以及解決措施
五寄摆、總結(jié)
一谅辣、關(guān)于項目
開發(fā)環(huán)境:Xcode 9.4.1,語言:Swift4.1
代碼下載:代碼下載地址,歡迎點贊和反饋
開發(fā)備注:對此項目一些數(shù)據(jù)是使用抓包工具進行獲取,而圖片的獲取也是通過itool下載而取之婶恼。對于Charles抓包工具如果有想了解和使用的話可以查看筆者之前的文章桑阶。
然而抓包工具并不是萬能的,對于有些接口傳參方式是加密的勾邦,比如登錄,比如調(diào)用某個接口的前提是必須先登錄蚣录,然后才能掉下一個接口。咱就繞路而行吧眷篇。但是慶幸的是雖然有些接口雖不知以何種方式進行加密的萎河,但是至少能抓取到返回數(shù)據(jù),這就足夠了蕉饼。我們只要拿到返回的數(shù)據(jù)就好了虐杯。至少這個對我們只是想拿個項目練練手的話。就已經(jīng)可以算是很人性化了昧港。
二擎椰、效果預(yù)覽
三、詳細講解
3.0 歡迎模塊分析(引導(dǎo)頁和廣告頁)
當(dāng)程序被打開時,在創(chuàng)建KeyWindow的RootViewController時判斷是否是首次打開,這里的邏輯是如果用戶是首次打開應(yīng)用則顯示引導(dǎo)頁(引導(dǎo)圖片資料找了老半天沒有找著慨飘,就直接從網(wǎng)絡(luò)上拉了幾張圖片進來),當(dāng)點擊引導(dǎo)頁最后一頁的立即體驗直接進入TabBarController,不顯示廣告頁(效果如下圖)
如果用戶不是首次打開應(yīng)用的話,則請求網(wǎng)絡(luò)加載圖片顯示廣告頁,并且在3秒后自動進入TabBarController(效果如下圖)
邏輯代碼如下:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.backgroundColor = UIColor.white
//加載歡迎頁面
initLinkPage()
window?.makeKeyAndVisible()
return true
}
func initLinkPage() {
// 用來判斷是否是第一次加載
let isFristOpen = UserDefaults.standard.object(forKey: "isFristOpen")
if isFristOpen == nil {
let guideVC = FMGuideViewController()
guideVC.finishBtnClickCallBack = {[weak self] () -> Void in
self?.initRootViewController()
}
window?.rootViewController = guideVC
UserDefaults.standard.set("isFristOpen", forKey: "isFristOpen")
}else{
loadAdViewController()
}
}
func loadAdViewController(){
let adVC = FMAdViewController()
adVC.skipBtnClickCallBack = { () -> Void in
self.initRootViewController()
}
window?.rootViewController = adVC
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
self.initRootViewController()
}
}
func initRootViewController() {
window?.rootViewController = BaseTabBarViewController()
}
這塊的邏輯代碼總共都不超過50行确憨,至于引導(dǎo)頁和廣告頁的具體實現(xiàn)代碼,就不詳細講述了,可下載源碼進行查看
3.1 登錄模塊分析
由于登錄接口請求方式加密了译荞,故我們采取別的方式進行模擬用戶是否登錄
如下圖:
接下來打開mocky網(wǎng)站,將上圖返回的數(shù)據(jù)復(fù)制然后請繼續(xù)往下看
最后就可以通過該mocky生成的接口進行網(wǎng)絡(luò)請求模擬請求了,接下來后面別的接口如遇到加密情況也將采取此種方式進行處理休弃。
緊接著對用戶登錄的一些信息進行保存吞歼,下面貼一段歸檔的代碼
//用戶模型數(shù)據(jù)
import UIKit
import HandyJSON
class UserInfoModel: NSObject,HandyJSON,NSCoding {
required override init() { }
var token: String? //戶授權(quán)的唯一票據(jù)
var uid: String? //用戶的uid
var ret: Int = 1 //返回的狀態(tài)碼 0: 表示成功
var isLogin: Bool{
if LoginHelper.sharedInstance.userInfo?.token != nil {
return true
}else {
return false
}
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(token, forKey: "token")
aCoder.encode(uid, forKey: "uid")
}
required init?(coder aDecoder: NSCoder) {
token = aDecoder.decodeObject(forKey: "token") as? String
uid = aDecoder.decodeObject(forKey: "uid") as? String
}
}
登錄信息的數(shù)據(jù)保存以及退出登錄的本地數(shù)據(jù)清理都交由LoginHelper來處理
import UIKit
import Foundation
var instance:LoginHelper? = nil
class LoginHelper: NSObject {
var userInfo:UserInfoModel? {
didSet{
guard userInfo != nil else {
return
}
}
}
static let sharedInstance: LoginHelper = {
instance = LoginHelper()
instance?.userInfo = UserInfoModel()
let saveModel = NSKeyedUnarchiver.unarchiveObject(withFile: UserDataFilePath)
print("path:\(UserDataFilePath)")
if (saveModel != nil) {
instance?.userInfo = (saveModel as! UserInfoModel)
}
return instance!
}()
//MARK:保存用戶信息
func saveUserInfo(userInfo: UserInfoModel) {
NSKeyedArchiver.archiveRootObject(userInfo, toFile: UserDataFilePath)
}
//MARK:清除用戶信息
func clearUserInfo() {
instance = nil
userInfo?.token = nil
let clearUserInfo:Bool = ((try? FileManager.default.removeItem(atPath: UserDataFilePath)) != nil)
clearUserInfo ? print("清除用戶數(shù)據(jù)成功"):print("清除用戶數(shù)據(jù)失敗")
}
//MARK:登錄成功
class func loginSuccessDataHandle(){
NotificationCenter.default.post(name: NSNotification.Name(kLoginSuccessNotification), object: nil)
}
//MARK:退出登錄 數(shù)據(jù)清理
class func loginOutDataHandle() {
LoginHelper.sharedInstance.clearUserInfo()
NotificationCenter.default.post(name: NSNotification.Name(kLogOutNotification), object: nil)
}
}
布局:登錄頁面布局有點粗糙,直接采取的是Xib的布局方式塔猾。
登錄成果之后保存用戶數(shù)據(jù)篙骡,再者登錄成功之后要告訴相關(guān)頁面做相應(yīng)的UI更新
NetworkTool.shareNetworkTool().request(methodType: .GET, baseUrl: MAIN_URL_MOCKY, urlString: kLoginUrl, parameters: [:]) { (result, error) in
guard let resultDic = result as? [String : AnyObject] else{
return
}
let infoModel:UserInfoModel = UserInfoModel.deserialize(from: resultDic)!
if infoModel.ret == 0 {
LoginHelper.sharedInstance.userInfo = infoModel
LoginHelper.sharedInstance.saveUserInfo(userInfo: infoModel)
LoginHelper.loginSuccessDataHandle()
self.dismiss(animated: true, completion: nil)
}
}
3.2 首頁模塊分析
首頁的數(shù)據(jù)較復(fù)雜一些,字母里面嵌套數(shù)組丈甸,數(shù)組里面再嵌套字典糯俗,字典又有數(shù)組字典。同時返回來的數(shù)據(jù)也相對其他接口多睦擂。首先我們先看推薦模塊的接口數(shù)據(jù)
頂部的banner(FMHomeHeaderView繼承于TYCyclePagerView)得湘,作為 tableView.tableHeaderView 作為處理,而(猜你喜歡顿仇、精品淘正、懶人一鍵聽)作為viewForHeaderInSection,其他部分根據(jù)判斷而返回不同樣式的Cell臼闻。
3.3 我聽模塊分析
我聽模塊頂部為自定義ListenHeaderView,下面為使用LTScrollView管理三個子模塊的滾動視圖鸿吆,訂閱接口由于涉及用戶登錄相關(guān),則拿不到實時接口述呐,故采用Mocky模擬網(wǎng)絡(luò)請求惩淳。而一鍵聽和推薦可以抓到其接口并直接可以請求到數(shù)據(jù),故直接拿的原生接口進行數(shù)據(jù)請求乓搬,且推薦頁面做了上拉刷新以及下拉加載更多思犁。訂閱中的Cell和推薦中的cell采取的公用方式,只是稍微有點不一樣而已缤谎,像這樣大致一樣的就沒有必要再寫多寫一個cell了抒倚。一鍵聽模塊其中有個跑馬燈滾動顯示的效果,點擊添加頻道坷澡,跳轉(zhuǎn)更多頻道界面托呕,(在頁面跳轉(zhuǎn)時,則統(tǒng)一在BaseNavViewController里面添加了返回按鈕频敛,以及側(cè)滑返回项郊。)該界面為雙TableView實現(xiàn)聯(lián)動效果,點擊左邊分類LeftTableView對應(yīng)右邊RightTableView滾動到指定分區(qū)斟赚,滾動右邊RightTableView對應(yīng)的左邊LeftTableView滾動到對應(yīng)分類着降。
3.4 播放模塊分析(待完善)
3.5 發(fā)現(xiàn)模塊分析
發(fā)現(xiàn)頁面總成分成兩大部分,其中共涉及6個接口(綠色和藍色框框表示需要請求的接口)拗军。故采取MVVM的方式實現(xiàn)任洞,將接口請求全部放在ViewModel蓄喇,然后再根據(jù)需求進行數(shù)據(jù)回調(diào)。
因為登錄后的關(guān)注界面和推薦界面跟這個差不多交掏,所以直接服用推薦里面的cell妆偏。
未登錄的關(guān)注則采用DZNEmptyDataSet開源框架,該框架挺不錯的盅弛,筆者一直在使用钱骂。該框架目前已有1萬多顆小星星。
這里重點在于 圖片展示的數(shù)量計算挪鹏,以及根據(jù)文字內(nèi)容和圖片的張數(shù)計算當(dāng)前Cell的高度见秽。
實現(xiàn)思路: 這里cell主要是通過xib畫的,我們需要把collectView的寬度和高度進行拖線讨盒,然后通過圖片數(shù)量拿到對應(yīng)的寬高度解取。然后設(shè)置collectionView寬高度的constant。緊接著拿到bottomView的最大Y值催植, 并將高度保存到viewModel模型中
計算圖片的思路
private func calculatePicViewSize(count:Int) -> CGSize {
/**
圖片顯示分幾種情況:
1.沒有配圖
2.4張配圖
3.其他張配圖 (count -1)/3 + 1 = rows
*/
//1.沒有配圖
if count == 0 {
collectionViewBottomConst.constant = 0
return CGSize(width: 0, height: 0)
}
collectionViewBottomConst.constant = 10
// 取出picView對應(yīng)的layout
let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout
//圖片的WH
let imageViewWH = (screenW - 2 * magin - 2 * iteMagin) / 3
layout.itemSize = CGSize(width: imageViewWH, height: imageViewWH)
//2. 4張配圖
if count == 4 {
let picViewWH = imageViewWH * 2 + iteMagin + 1 //+1微調(diào)
return CGSize(width: picViewWH, height: picViewWH)
}
// 3.其他張配圖 (count -1)/3 + 1 = rows
/** 例子: 5張配圖 2行 row:(5-1)/3+1 = 2*/
// 4.1 計算行數(shù)
let rows = CGFloat( (count - 1 )/3 + 1)
// 4.2 計算高度
let picViewH = rows * imageViewWH + (rows - 1 ) * iteMagin
// 4.3 計算寬度
let picViewW = screenW - 2 * magin
return CGSize(width: picViewW, height: picViewH)
}
3.6 我的模塊分析
根據(jù)用戶是否登錄而進行一些業(yè)務(wù)邏輯的處理以及顯示肮蛹, 其中只做了掃一掃勺择,退出登錄(需清除本地數(shù)據(jù))创南,就沒做那些詳細界面了。上面的cell用xib直接畫的省核,下面的Cell根據(jù)dataArr進行分區(qū)顯示及每個分區(qū)的count稿辙。
參考寫法
http://www.reibang.com/p/879f58fe3542
喜馬拉雅FM
https://juejin.im/post/5b97743df265da0af21351aa#heading-11
下廚房
http://www.reibang.com/p/a8f619a2c622
四、遇到的問題以及解決措施
問題一: 導(dǎo)入HandyJSON報錯
原因:
"_swift_getFieldAt", referenced from:
這個問題在 HandyJSON 4.2.0 版本 + XCcode Version 9.4.1 (9F2000) + swift 4.1 里面存在气忠,但降級到 HandyJSON 4.1.3 就沒有問題了邻储。
額外提醒:
HandyJSON 開源框架中readme 里有說明
4.2.0 版本只支持 swift 4.2, swift 4.1 就用 4.1.3 版本
解決措施:
此時我們只要在Podfile
注明使用哪個基本即可 旧噪。例如:pod 'HandyJSON', '~> 4.1.3 '
問題二: MAC使用Charles吨娜,手機設(shè)置代理后,網(wǎng)頁無法打開
解決措施:
點擊Install Charles Root Certifficate會進入鑰匙串中淘钟,之后
設(shè)置完了之后就可以正常訪問網(wǎng)絡(luò)勒
問題三: import JXMarqueeView 報錯
解決措施:
General->Deployment Target -> 9.0 即可
最開始我的這里是8.0, 改完之后編譯一下即可
五宦赠、總結(jié)
目前項目中主要模塊的界面和功能(一級界面)基本完成,使用XIB以及SnapKit開源框架(相對于OC中的Masonry)進行布局米母。接下來
1勾扭、缺失功能的完善
2、對當(dāng)前模塊進行一些Bug修改和調(diào)整
3铁瞒、根據(jù)返回數(shù)據(jù)進行細節(jié)優(yōu)化以及調(diào)整
4妙色、少玩多學(xué),年尾已快到來慧耍,需對年初計劃進行歸納和總結(jié)