为了账号安全,请及时绑定邮箱和手机立即绑定

Swift高仿喜马拉雅FM(Swift4.1)

标签:
Swift

webp

前言
一直想利用空余时间写个开源项目,兜兜转转许久,光说不练都是空把戏,咱撸起袖子就是干。说做咱就开始利用空闲时间骚动起来吧。本开源项目讲解了一些App常见功能界面的搭建以及实现思路,适合新手以及正在学习Swift的同胞们。

号外:最近似乎把自己活成了一个网瘾少年般的模样,一到某个点就开始相约鸡场。着实尴尬!!!

目录
一、关于项目
二、效果预览
三、详细讲解
3.0 欢迎模块分析
3.1 首页模块分析
3.2 我听模块分析
3.3 播放模块分析
3.4 发现模块分析
3.5 我的模块分析
四、遇到的问题以及解决措施
五、总结

一、关于项目

开发环境:Xcode  9.4.1,语言:Swift4.1
代码下载:代码下载地址,欢迎点赞和反馈
开发备注:对此项目一些数据是使用抓包工具进行获取,而图片的获取也是通过itool下载而取之。对于Charles抓包工具如果有想了解和使用的话可以查看笔者之前的文章。
然而抓包工具并不是万能的,对于有些接口传参方式是加密的,比如登录,比如调用某个接口的前提是必须先登录,然后才能掉下一个接口。咱就绕路而行吧。但是庆幸的是虽然有些接口虽不知以何种方式进行加密的,但是至少能抓取到返回数据,这就足够了。我们只要拿到返回的数据就好了。至少这个对我们只是想拿个项目练练手的话。就已经可以算是很人性化了。

webp

喜马拉雅FM(项目搭建及层次结构剖析).png

webp

项目中的方法层次图.png


二、效果预览

webp

首页.gif

webp

我听.gif

webp

发现.gif

webp

我的.gif


三、详细讲解

3.0 欢迎模块分析(引导页和广告页)

当程序被打开时,在创建KeyWindow的RootViewController时判断是否是首次打开,这里的逻辑是如果用户是首次打开应用则显示引导页(引导图片资料找了老半天没有找着,就直接从网络上拉了几张图片进来),当点击引导页最后一页的立即体验直接进入TabBarController,不显示广告页(效果如下图)


webp

引导页.gif

如果用户不是首次打开应用的话,则请求网络加载图片显示广告页,并且在3秒后自动进入TabBarController(效果如下图)


webp

广告页

逻辑代码如下:

    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行,至于引导页和广告页的具体实现代码,就不详细讲述了,可下载源码进行查看


3.1 登录模块分析

由于登录接口请求方式加密了,故我们采取别的方式进行模拟用户是否登录
如下图:


webp

image.png

接下来打开mocky网站,将上图返回的数据复制然后请继续往下看

webp

mocky.gif


最后就可以通过该mocky生成的接口进行网络请求模拟请求了,接下来后面别的接口如遇到加密情况也将采取此种方式进行处理。

紧接着对用户登录的一些信息进行保存,下面贴一段归档的代码

//用户模型数据import UIKitimport HandyJSONclass UserInfoModel: NSObject,HandyJSON,NSCoding {
    required override init() { }
    var token: String? //户授权的唯一票据
    var uid: String? //用户的uid
    var ret: Int = 1 //返回的状态码  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
        
    }
}

登录信息的数据保存以及退出登录的本地数据清理都交由LoginHelper来处理

import UIKitimport Foundation
var instance:LoginHelper? = nilclass 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("清除用户数据成功"):print("清除用户数据失败")
    }    
    //MARK:登录成功
    class func loginSuccessDataHandle(){
        NotificationCenter.default.post(name: NSNotification.Name(kLoginSuccessNotification), object: nil)
    }    //MARK:退出登录 数据清理
    class func loginOutDataHandle() {
        LoginHelper.sharedInstance.clearUserInfo()
        NotificationCenter.default.post(name: NSNotification.Name(kLogOutNotification), object: nil)
    }
}

布局:登录页面布局有点粗糙,直接采取的是Xib的布局方式。

登录成果之后保存用户数据,再者登录成功之后要告诉相关页面做相应的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 首页模块分析

首页的数据较复杂一些,字母里面嵌套数组,数组里面再嵌套字典,字典又有数组字典。同时返回来的数据也相对其他接口多。首先我们先看推荐模块的接口数据

webp

图片.png

顶部的banner(FMHomeHeaderView继承于TYCyclePagerView),作为        tableView.tableHeaderView 作为处理,而(猜你喜欢、精品、懒人一键听)作为viewForHeaderInSection,其他部分根据判断而返回不同样式的Cell。

3.3 我听模块分析

我听模块顶部为自定义ListenHeaderView,下面为使用LTScrollView管理三个子模块的滚动视图,订阅接口由于涉及用户登录相关,则拿不到实时接口,故采用Mocky模拟网络请求。而一键听和推荐可以抓到其接口并直接可以请求到数据,故直接拿的原生接口进行数据请求,且推荐页面做了上拉刷新以及下拉加载更多。订阅中的Cell和推荐中的cell采取的公用方式,只是稍微有点不一样而已,像这样大致一样的就没有必要再写多写一个cell了。一键听模块其中有个跑马灯滚动显示的效果,点击添加频道,跳转更多频道界面,(在页面跳转时,则统一在BaseNavViewController里面添加了返回按钮,以及侧滑返回。)该界面为双TableView实现联动效果,点击左边分类LeftTableView对应右边RightTableView滚动到指定分区,滚动右边RightTableView对应的左边LeftTableView滚动到对应分类。

webp

图片.png


3.4 播放模块分析(待完善)


3.5 发现模块分析

发现页面总成分成两大部分,其中共涉及6个接口(绿色和蓝色框框表示需要请求的接口)。故采取MVVM的方式实现,将接口请求全部放在ViewModel,然后再根据需求进行数据回调。
因为登录后的关注界面和推荐界面跟这个差不多,所以直接服用推荐里面的cell。
未登录的关注则采用DZNEmptyDataSet开源框架,该框架挺不错的,笔者一直在使用。该框架目前已有1万多颗小星星。

webp

图片.png

这里重点在于 图片展示的数量计算,以及根据文字内容和图片的张数计算当前Cell的高度。
实现思路: 这里cell主要是通过xib画的,我们需要把collectView的宽度和高度进行拖线,然后通过图片数量拿到对应的宽高度。然后设置collectionView宽高度的constant。紧接着拿到bottomView的最大Y值, 并将高度保存到viewModel模型中


webp

图片.png

计算图片的思路

    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对应的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微调
            return CGSize(width: picViewWH, height: picViewWH)
        }        
        // 3.其他张配图 (count -1)/3 + 1  = rows
        /**   例子:  5张配图  2行   row:(5-1)/3+1 = 2*/
        // 4.1 计算行数
        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 我的模块分析
根据用户是否登录而进行一些业务逻辑的处理以及显示, 其中只做了扫一扫,退出登录(需清除本地数据),就没做那些详细界面了。上面的cell用xib直接画的,下面的Cell根据dataArr进行分区显示及每个分区的count。

webp



作者:flowerflower
链接:https://www.jianshu.com/p/c9258d9855a5


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消