再读斋

iOS开发之07视图控制器与导航模式

模态视图

在导航过程中,有时候需要放弃主要任务而做其他次要任务,然后在返回到次要任务,这个次要任务就是在模态视图中完成的,如注册中主要任务是登录后进入主界面,如果用户没有注册,就要先去注册,注册是次要任务,当用户注册完成后,它会关闭注册视图,回到登录界面继续进行主要任务。

默认情况下,模态视图是从屏幕下方滑出来的。

负责控制器模态视图的控制器称为模态视图控制器,它并不是一个专门的类,它可以是前面提到的控制器的子类。负责主要任务视图的控制器称为主视图控制器。在UICOntrollerView中,主要有如下两个方法:

  • -present():呈现视图
  • -dismiss():关闭视图

在呈现模态视图时有两种方式:一是通过使用UIViewController的present方法实现;二是通过故事板的“过渡”(Segue)实现。

下面我们通过登录案例来介绍模态视图

步骤

  1. 创建一个iOS工程,将当前控制器嵌入到一个导航控制器中,具体步骤是:在故事板中选择View Controller,然后点击Xcode菜单栏Editor-Embed In-Navigation Controller菜单就会自动创建一个导航视图
  2. 点击导航栏,将导航栏的标题设为登录,然后从对象库中拖入Lable、TextField、Button等控件
  3. 接下来设计第二个界面,先从对象库中拖入一个View Controller到设计界面中,然后参考步骤1将该视图控制器嵌入到导航控制器中,修改该导航栏标题为注册,然后从对象库中拖入两个Bar Button Item到导航栏两边,分别设置identifier属性为Cancel和Save
  4. 接下来需要在登录场景和注册场景创建一个过渡,按住control键,从登录界面的注册按钮拖鼠标到注册导航控制器,然后松开鼠标,在弹出的视图框中选择Present Modally菜单;它是模态类型的过渡
  5. 最后,添加注册控制器类,创建一个类RegisterViewController集成UIViewController,然后回到故事板中将注册视图的Class选择为RegisterViewController

代码实现

ViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//
// ViewController.swift
// ModalViewSample
//
// Created by Michael on 2016/11/9.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var mUserName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// 注册消息
NotificationCenter.default.addObserver(self, selector: #selector(self.register(_ :)), name: NSNotification.Name(rawValue: "RegisterCompletion"), object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.init(rawValue: "RegisterCompletion"), object: nil)
}
func register(_ notification : Notification) {
let text = notification.userInfo?["username"] as? String
mUserName.text = text!
NSLog("%@",text!)
}
}

RegisterViewController.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//
// RegisterViewController.swift
// ModalViewSample
//
// Created by Michael on 2016/11/9.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class RegisterViewController: UIViewController {
@IBOutlet weak var mName: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func save(_ sender: Any) {
let userInfo = ["username":self.mName.text!]
//发送消息
NotificationCenter.default.post(name: NSNotification.Name.init(rawValue: "RegisterCompletion"), object: nil, userInfo: userInfo)
self.dismiss(animated: true, completion: {
})
}
@IBOutlet weak var save: UIBarButtonItem!
@IBAction func cancel(_ sender: Any) {
self.dismiss(animated: true, completion: {
})
}
}

效果图

分屏导航

基于分屏导航是平铺导航的主要实现方式,涉及的主要控件有分屏控件UIPageControll和屏幕滚动视图UIScrollView,一般不超过10屏

步骤

  1. 创建iOS工程,从对象库中拖入UIPageControll和UIScrollView到故事板中,并将其放到合适的位置,UIPageControll放在靠底部,UIScrollView全屏显示,将视图的背景设为黑色
  2. 选中UIScrollView的属性检查器,设置不显示Scroll View的Indicator,同时选择滚动Scrolling Enable和分屏Paging Enable。分屏属性是Scroll View每次滑动时翻一屏
  3. 选择Page Controll的属性检查器,设置Pages中的of pages总屏数为3,Current当前位置为0,并修改其宽度为300,它的高度是不能修改的。
  4. 最后为这两个控件定义输出口并连接注册到ViewController类中,为Page Controll控件定义响应屏幕变化事件的方法。-changPage

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
//
// ViewController.swift
// PageControlNavigation
//
// Created by Michael on 2016/11/10.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class ViewController: UIViewController,UIScrollViewDelegate {
@IBOutlet weak var mScrollView: UIScrollView!
@IBOutlet weak var mPageControl: UIPageControl!
var mImage1: UIImageView!
var mImage2: UIImageView!
var mImage3: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.mScrollView.delegate = self
self.mScrollView.contentSize = CGSize(width: self.view.frame.size.width * 3, height: self.mScrollView.frame.size.height)
self.mScrollView.frame = self.view.frame
self.mImage1 = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 480))
self.mImage1.image = UIImage(named: "达芬奇-蒙娜丽莎")
self.mScrollView.addSubview(mImage1)
self.mImage2 = UIImageView(frame: CGRect(x: self.view.frame.size.width, y: 0, width: self.view.frame.size.width, height: 480))
self.mImage2.image = UIImage(named: "罗丹-思想者")
self.mScrollView.addSubview(mImage2)
self.mImage3 = UIImageView(frame: CGRect(x: self.view.frame.size.width * 2, y: 0, width: self.view.frame.size.width, height: 480))
self.mImage3.image = UIImage(named: "保罗克利-肖像")
self.mScrollView.addSubview(mImage3)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//UIPageControll事件处理
@IBAction func changePage(_ sender: Any) {
UIView.animate(withDuration: 0.3, animations: {
let whichPage = self.mPageControl.currentPage
//设置内容视图坐标原点与屏幕滚动视图坐原点的偏移量
self.mScrollView.contentOffset = CGPoint(x: 320 * whichPage, y: 0)
})
}
//屏幕滚动视图事件处理方法å
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
self.mPageControl.currentPage = Int(offset.x) / 320
}
}

效果图

分页控制器

在iOS5以后,我们可以使用分页控制器UIPageViewController构建类似于电子书效果的应用。

分页控制器需要放置在一个父视图控制器中,在分页控制器下面还要有子视图控制器,每个子视图控制器对应一个页面。

UIPageViewController没有对应的视图,我们需要使用代码来实现;需要在UIPageViewController所在的控制器实现UIPageViewControllerDelegate和UIPageViewControllerDataSource协议,UIPageViewControllerDataSource数据源协议必须要实现的方法有以下两个:

  • func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController)
  • func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController)

UIPageViewControllerDelegate委托协议中一般实现的方法是:

  • func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation)
  • func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool)

步骤

  1. 创建一个iOS工程
  2. 代码实现UIPageViewController

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//
// ViewController.swift
// PageNavigation
//
// Created by Michael on 2016/11/10.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
enum DirectionFroward : Int {
case Before = 1 //向前
case After = 2 //向后
}
class ViewController: UIViewController,UIPageViewControllerDelegate,UIPageViewControllerDataSource {
//当前Page的索引
var mPageIndex = 0
var direct = DirectionFroward.After
var mPageViewController : UIPageViewController!
var mViewControllers : [UIViewController]!
override func viewDidLoad() {
super.viewDidLoad()
let pageViewController1 = UIViewController()
let pageViewController2 = UIViewController()
let pageViewController3 = UIViewController()
self.mViewControllers = [pageViewController1,pageViewController2,pageViewController3]
let mImage1 = UIImageView(frame: self.view.frame)
mImage1.image = UIImage(named: "达芬奇-蒙娜丽莎")
pageViewController1.view.addSubview(mImage1)
let mImage2 = UIImageView(frame: self.view.frame)
mImage2.image = UIImage(named: "罗丹-思想者")
pageViewController2.view.addSubview(mImage2)
let mImage3 = UIImageView(frame: self.view.frame)
mImage3.image = UIImage(named: "保罗克利-肖像")
pageViewController3.view.addSubview(mImage3)
//transitionStyle: pageCurl表示翻书效果样式 scroll 滑屏效果样式
//navigationOrientation 水平和垂直方向
self.mPageViewController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil)
self.mPageViewController.delegate = self
self.mPageViewController.dataSource = self
//设置首页
//direction forward向前 reverse向后
self.mPageViewController.setViewControllers([pageViewController1], direction: .forward, animated: true, completion: nil)
self.view.addSubview(self.mPageViewController.view)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//DataSource协议
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
NSLog("向前翻")
mPageIndex -= 1
if mPageIndex < 0 {
mPageIndex = 0;
return nil
}
direct = .Before
return self.mViewControllers[mPageIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
NSLog("向后翻")
mPageIndex += 1
if mPageIndex > 2 {
mPageIndex = 2
return nil
}
direct = .After
return self.mViewControllers[mPageIndex]
}
//Delegate协议
func pageViewController(_ pageViewController: UIPageViewController, spineLocationFor orientation: UIInterfaceOrientation) -> UIPageViewControllerSpineLocation {
self.mPageViewController.isDoubleSided = false
//min和max 首页显示一个视图 mid首页显示两个视图
return .min
}
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
//翻页未成功
if completed == false {
if direct == .After {
mPageIndex -= 1
}
if direct == .Before {
mPageIndex += 1
}
}
}
}

效果图

标签导航

使用标签栏时有一定的指导原则:标签栏位于屏幕下方,占有49个像素屏幕空间,有时可以隐藏起来;标签栏中的标签不能超过5个,如果超过5个则最后一个显示为更多,点击更多标签会出现更多的列表。

步骤

在开发具体应用的时候,标签导航的各个标签分别代表一个功能模块,各功能模块之间相对独立。

  1. 创建一个iOS工程模板Tabbed Application应用,默认创建两个标签
  2. 从对象库中拖入一个ViewController到故事板中
  3. 添加ViewController和Tab Controller Scene的连线,具体操作是:按住control键从Tab Controller Scene拖曳鼠标到ViewController,释放鼠标,从弹出窗口中选择view controllers项即可
  4. 设置三个场景的标题
  5. 分别为三个场景创建三个视图类

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// HeiViewController.swift
// TabNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class HeiViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// JiViewController.swift
// TabNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class JiViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// LiaoViewController.swift
// TabNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class LiaoViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}

效果图

树形导航

树形导航模式是将导航控制器UINavigationController与表视图结使用,主要用于构建从属关系的导航。下面我们创建一个三级视图的树形导航示例。

步骤

  1. 创建iOS工程,使用UINavigationController创建以及表视图
  2. 创建二级表视图CitiesViewController
    • 从对象库中拖入一个Table View Controller到对象库中作为二级视图控制器
    • 按住control键,从上一个Root View Controller的单元格中拖动鼠标到当前添加的Table View Controller中,释放鼠标,在弹出窗口中选择Show选项
    • 选择连接线中的过渡Segue,打开其属性检查器,然后在Indentifier属性中输入ShowCities
  3. 创建三级视图DetailViewController
    • 从对象库中拖入一个View Controller到对象库中作为三级视图控制器
    • 按住control键,从上一个Table View Controller的单元格中拖动鼠标到当前添加的View Controller中,释放鼠标,在弹出窗口中选择Show选项
    • 选择连接线中的过渡Segue,打开其属性检查器,然后在Indentifier属性中输入ShowDetail
  4. 设置各级视图的Table View Cell的属性

代码实现

一级视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//
// ViewController.swift
// TreeNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class ViewController: UITableViewController {
var dictData:NSDictionary!
var listData:NSArray!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self
self.tableView.dataSource = self
let path = Bundle.main.path(forResource: "provinces_cities", ofType: "plist")
self.dictData = NSDictionary(contentsOfFile: path!)
self.listData = self.dictData.allKeys as NSArray
self.title = "省份信息"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "Custom", for: indexPath)
let row = indexPath.row
cell.textLabel?.text = self.listData[row] as? String
return cell
}
//场景过渡之前的处理 点击表视图的单元格触发
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowCities" {
let indexPath = self.tableView.indexPathForSelectedRow! as IndexPath
let selectedIndex = indexPath.row
//获取要跳转到的视图控制器对象
let controller = segue.destination as! CitiesTableViewController
let selectName = self.listData[selectedIndex] as! String
controller.listData = self.dictData[selectName] as! NSArray
controller.title = selectName
}
}
}

二级视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//
// CitiesTableViewController.swift
// TreeNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
class CitiesTableViewController: UITableViewController {
var listData:NSArray!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.listData.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "CityCell", for: indexPath)
let row = indexPath.row
let dict = self.listData[row] as! NSDictionary
cell.textLabel?.text = dict["name"] as? String
return cell
}
//场景过渡之前的与处理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowDetail" {
let indexPath = self.tableView.indexPathForSelectedRow! as IndexPath
let selectIndex = indexPath.row
let dict = self.listData[selectIndex] as! NSDictionary
//获取要跳转到的视图控制器对象
let controller = segue.destination as! DetailViewController
controller.url = dict["url"] as! String
controller.title = dict["name"] as? String
}
}
}

三级视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// DetailViewController.swift
// TreeNavigation
//
// Created by Michael on 2016/11/15.
// Copyright © 2016年 Michael. All rights reserved.
//
import UIKit
import WebKit
class DetailViewController: UIViewController,WKNavigationDelegate {
var url:String!
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
NSLog(url)
self.webView = WKWebView(frame: self.view.frame)
view.addSubview(webView)
webView.navigationDelegate = self
//let nUrl = URL(string: "https://baike.baidu.com/view/2172.htm")
let mUrl = URL(string: url)
let request = URLRequest(url: mUrl!)
webView.load(request)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
NSLog("开始加载")
}
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
NSLog("内容开始返回时回调")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
NSLog("加载完成")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
NSLog("加载失败")
}
}

效果图

刘涤生 wechat