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