iOS之引导页和登录界面的搭建思路

在市面上几乎所有的APP中,基本上都有个引导页面,有的还具有一些登录的功能,更有甚者还需要选择每个区域的服务器的节点的操作,我们公司现在做的应用就有这种需求。

大致的实现思路

引导界面,一般都会放几张轮播图,我们将在轮播到最后一张图片的时候显示出跳转到登录界面的按钮,然后在登录界面中会创建选择服务器的按钮,然后再次跳转到选择服务器节点的界面。大致的流程就是这样。
在轮播到最后一张图片的时候,创建跳转按钮,在这个跳转的操作中,一般会更改掉本个窗口的根控制器,将根控制器从引导界面转为登录界面。当进行选择服务器节点的操作的时候,这里就不需要进行根控制器的切换了。

具体的实现过程

此demo的数据,我是放在本地操作的,数据加载是通过plist文件进行的。

1、搭建引导页面

在引导页面中,我们会创建一个轮播器,来展示轮播的图片。在轮播界面中,往往需要隐藏导航栏的。

    /// 导航栏设置
    func wjNavgationSettings() {
        self.navigationController?.navigationBar.isHidden = true
    }

在页面即将要加载完成的时候就要隐藏掉导航栏。
导航栏隐藏掉后,为了页面的美观,一般也会隐藏掉状态栏,所以在当前控制器还需要添加隐藏状态栏的代码。

    // 隐藏状态栏
    override var prefersStatusBarHidden: Bool{
        return true
    }

加载轮播图片:

    self.images = NSMutableArray(capacity: 0)
    let imageArray = NSArray(contentsOfFile: Bundle.main.path(forResource: "data", ofType: "plist")!)
    if let imgArr = imageArray {
        self.images.addObjects(from: imgArr as Array)
    }

这样就把plist的数据获取得到了。
搭建轮播器

let imageCount : Double = Double(self.images.count)
let screenW = self.view.frame.size.width
let screenH = self.view.frame.size.height
// 创建轮播图
let scrollView = UIScrollView()
scrollView.frame = self.view.frame
scrollView.isPagingEnabled = true
scrollView.bounces = false
scrollView.contentSize = CGSize(width: Double(screenW) * imageCount, height: 0) // 页面的展示的大小
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
scrollView.backgroundColor = UIColor.brown
scrollView.delegate = self
self.view.addSubview(scrollView)
self.scrollView = scrollView

创建图片展示的imageView以及pageControl。
根据图片的张数来循环创建imageView和创建pageControl

        // 创建图片
        for i in 0..<Int(imageCount) {
            let imageView = UIImageView()
            imageView.image = UIImage(named: "gagi\(i + 1)")
            imageView.frame = CGRect(x: Double(screenW) * Double(i), y: 0, width: Double(screenW), height: Double(screenH))
            imageView.isUserInteractionEnabled = true // 为的是响应链能够被传递,不然后添加在其上面的按钮的点击事件就不能被执行了
            scrollView.addSubview(imageView)
            self.imageView = imageView
        }
        // pageControl
        let pageControl = UIPageControl()
        pageControl.frame = CGRect(x: 137.5, y: 600.0, width: 100.0, height: 20.0) // 暂且写死数据
        pageControl.isUserInteractionEnabled = false
        pageControl.hidesForSinglePage = true
        pageControl.currentPage = 0
        pageControl.numberOfPages = Int(imageCount)
        self.view .addSubview(pageControl)
        self.pageControl = pageControl

以上代码应该就可以展示轮播的图片,但是会有个问题就是在轮播图片的时候,下面的pageControl不会有变化,所以就要scrollview遵守协议,在scrollview在停止滚动的时候就要去修改pageControl。

轮播图创建

遵守UIScrollViewDelegate,我们单独写在一个extension中,然后需要遵守此协议。
在scrollview进行滑动的时候,页面展示到最后一张图片的时候,应该创建出跳转按钮。

extension wjGuideVC : UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // 页面在移动过屏幕一般的时候就修改currentPage
        let page = Int(self.scrollView.contentOffset.x / scrollView.frame.size.width + 0.5) 
        self.pageControl.currentPage = page
        // 如果在最后一张照片出现的时候就要创建一个按钮,进行点击,然后跳转
        if page == self.images.count - 1 {
            let btn = UIButton(type: .custom)
            btn.frame = CGRect(x: 112.5, y: 500, width: 150.0, height: 50.0)
            btn.layer.masksToBounds = true
            btn.layer.cornerRadius = 10
            btn.backgroundColor = UIColor.cyan
            btn.setTitle("点 击 进 行 跳 转", for: .normal)
            btn.setTitleColor(UIColor.black, for: .normal)
            btn.addTarget(self, action: #selector(wjGuideVC.wjModifyRootVCAction(_ :)), for: .touchUpInside)
            self.imageView.addSubview(btn)
        }
    }
}
跳转按钮创建

以上的代码需要注意的地方就是,这个按钮是放在imageView上,也就是说如果没有写上imageView.isUserInteractionEnabled = true这句话,会导致这个事件的传递会被中断,在点击到按钮的时候,事件没法进行响应,也就没法事件传递到UIApplication,事件的分发也不会让按钮去处理,因为分发到imageView的时候就中断了,整个事件也会抛弃掉。所以加上这就话,就能把事件传递下去,然后事件的分发也会找到相应的按钮去执行。
以上的代码就基本完成了整个引导页面的UI搭建,但在按钮的点击事件中,该如何处理这个修改根控制器。
接下来就要知晓是谁要修改掉根控制器。
在app中,整个窗口就是个window,需要修改根控制器的也是这个window(其实window在程序中还有别的)。

在点击按钮后的操作:

  • 让导航栏显示出来
  • 记录引导页面已经显示过的数据存到本地去(还可以将本个app的版本号存到本地去)
  • 修改根控制器
  • 页面的跳转
    // 修改根控制器的按钮点击事件
    func wjModifyRootVCAction(_ btn : UIButton) {
        let vc = ViewController()
        vc.navigationController?.navigationBar.isHidden = false
        // 把已经出现过的引导页的结果记录到本地
        UserDefaults.standard.set(true, forKey: "isShowGuidePage")
        // 这个地方完全可以将app的version也存到本地去,然后在每次进入到app的时候就判断版本号
        let app = AppDelegate()
        let nav = UINavigationController(rootViewController: vc)
        app.window?.rootViewController = nav
        self.present(nav, animated: true, completion: nil)
    }

由于这个页面的跳转是通过present的形式跳转的,所以在跳转的时候会自动隐藏掉导航栏,即便是在下个控制器中让导航栏显示出来,也会隐藏掉的,所以要在下个界面显示出导航栏,就要推出整个导航控制器,这样就不会隐藏掉导航栏。
以上就是引导页面的全部逻辑。
在程序加载的时候,第一次加载会去加载查看本地有没存入引导页显示的记录,如果有的话就就直接使登录界面作为根控制器,如果没有就使得引导页面作为根控制器。

    func wjRootVCSettings() { // 可以将版本号也作为参考的依据。
        let isShowGuide = UserDefaults.standard.bool(forKey: "isShowGuidePage")
        if isShowGuide == true {
            let vc = ViewController()
            let nav = UINavigationController(rootViewController: vc)
            self.window?.rootViewController = nav            
        } else {
            let guideVC = wjGuideVC()
            let nav = UINavigationController(rootViewController: guideVC)
            self.window?.rootViewController = nav
        }
    }

2、登录界面

其实登录页面没啥好说,就demo而言就添加了一个跳转按钮还有两个textField。为了方便调试,不至于每次删掉app再进行调试,做了个重置的操作的按钮,是放在导航栏上的。

    // 导航栏设置
    func wjNavigationSettings() {
        self.title = "登录界面"
        // 恢复isShowGuidePage为false
        let btn = UIButton(type: .custom)
        btn.setTitle("重置", for: .normal)
        btn.setTitleColor(UIColor.black, for: .normal)
        btn.bounds = CGRect(x: 0, y: 0, width: 50, height: 30)
        btn.addTarget(self, action: #selector(ViewController.wjModifyDataAction(_ :)), for: .touchUpInside)
        self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: btn)
    }

    // 修改存在本地的数据
    func wjModifyDataAction(_ btn : UIButton) {
        UserDefaults.standard.set(false, forKey: "isShowGuidePage")
        self.dismiss(animated: true, completion: nil) // 只有当从引导页跳转过来才有效,如果是再次运行demo跳转进来的是无效的。堆栈中是没有创建引导页的。
    }
登录页面展示

3、服务器选择界面

也就是创建了两个textField,当两个输入框都有值的时候,按钮才能进行点击,加载数据。如果没有输入内容会有相应的提示。

提示输入内容

当输入框均有内容的时候,才加载tableView,然后在去加载数据,同样数据是通过plist文件进行加载的。

    // 数据加载
    // 懒加载
    lazy var wjServerListArr : NSMutableArray = {
        let dataArray = NSArray(contentsOfFile: Bundle.main.path(forResource: "serverListData", ofType: "plist")!)
        let wjServerListArr = NSMutableArray(capacity: 0)
        for dict in dataArray! {
            let dataDict = dict as! [String : String]
            let model = wjServerModel().wjServerModelWithDict(dataDict)
            wjServerListArr.add(model)
        }
        return wjServerListArr
    }()

界面的创建

    func wjCreatTableView() {
        let screenW = self.view.frame.size.width
        let screenH = self.view.frame.size.height
        let rect = CGRect(x: 0, y: 250, width: Double(screenW), height: Double(screenH - 250))
        UIView.animate(withDuration: 0.5) { 
            self.tableView = UITableView(frame: rect, style: UITableViewStyle.plain)
            self.tableView.backgroundColor = UIColor.white
            self.tableView.delegate = self
            self.tableView.dataSource = self
            self.view.addSubview(self.tableView)
        }
    }

协议的遵守,同样是放在extension中进行处理。便于管理。

// MARK:- UITableViewDataSource
extension wjServerSelectVC : UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.wjServerListArr.count;
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let iden = "serverCell"
        var cell = tableView.dequeueReusableCell(withIdentifier: iden)
        if cell == nil {
            cell = UITableViewCell(style: .subtitle, reuseIdentifier: iden)
        }
        let model = self.wjServerListArr[indexPath.row] as! wjServerModel
        cell?.textLabel?.text = model.serverName
        cell?.detailTextLabel?.text = model.serverIP
        return cell!
    }
}

// MARK:- UITableViewDelegate
extension wjServerSelectVC : UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 创建弹出框
//        let model = self.wjServerListArr[indexPath.row] as! wjServerModel
//        print("index is : \(model.serverName) : \(model.serverIP)")
        self.wjShowMessage(indexPath, "选择此服务器?") { (model) in
            print("index is : \(model.serverName) : \(model.serverIP)")
            self.dismiss(animated: true, completion: nil)
        }
    }
}
获取数据并展示以及点击效果

总结

其实整个demo的难度不大,也就是一些点需要注意。

  • 在创建scrollview的时候,整个页面的展示的大小和pageControl是根据图片的张数确定的。
  • 在创建引导页面的时候,隐藏掉导航栏和状态栏的方法。
  • 在跳转的时候,需要把导航栏显示出来的方法,就是退出导航控制器,而不是登录界面的控制器。
  • 需要在app进行加载的时候就要判断存放在本地的数据,根据这个数据来设置根控制器。
    代码
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,858评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,372评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,282评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,842评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,857评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,679评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,406评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,311评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,767评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,090评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,785评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,420评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,988评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,101评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,298评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,033评论 2 355

推荐阅读更多精彩内容