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

4,121 阅读7分钟

在市面上几乎所有的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进行加载的时候就要判断存放在本地的数据,根据这个数据来设置根控制器。 代码