UITabBarController 結合 UINavigationController、UITableViewController,在 iOS App 的 UI 設計中是比較經典的組合用法,效果能夠參考原生電話 App。git
本文咱們要實現的是,在點擊導航欄的按鈕後,隱藏 TabBar,顯示自定義的工具欄菜單,再次點擊按鈕切換回來。github
本文的 示例工程 已上傳至 Github,歡迎下載調試,完成後 App 顯示效果以下:編程
下面咱們從頭開始建立示例工程:swift
1.首先編程環境使用 Xcode 9.1
版本、Swift 4.0
語言,支持 iOS 10
,新建工程 Single View App
-> 工程名: SwitchBetweenCustomToolBarAndTabBar
。
2.新建文件,選擇 UITableViewController
模版,命名爲 MainTableViewController
。
3.再新建一個 UITableViewController
模版,命名爲 DetailTableViewController
。
4.在 Main.storyboard
中,刪除默認視圖,拖拽兩個 Table View Controller
,分別關聯至剛纔建立的 MainTableViewController
、DetailTableViewController
。
5.選擇 MainTableViewController
,在菜單欄選擇 Editor
-> Embed in
-> Navigation Controller
,效果以下:數組
6.在 StoryBoard
上對兩個表格作些基本設置,第一個表格設置爲 Dynamic Prototypes
、Grouped
、1 Rows
。選中 Cell,選擇 style Right Detail
,設置 Identifier
爲 bookCell
,Accessory 選擇 Disclosure Indicator
。選中視圖上的 Navigation Item
,設置標題爲:「書籍列表」。再拖入一個 Bar Button Item
放在導航欄右側,命名爲:「編輯」。app
7.第二個表格設置爲 Static Cells
、 Grouped
、 1 Sections
、 3 Rows
,再拖拽一個 Navigation Item
,並設置標題爲:「書籍詳情」。選中 Cell,選擇 style Right Detail
,修改標籤名稱。在兩個表格之間建立一個 Selection Segue
,選中第一個表格的 Cell,按住 control
鏈接至第二個表格,在 Selection Segue
下選擇 Show
,選擇 Segue
,設置 Identifier
爲 showDetail
。效果以下:ide
8.在 StoryBoard
中再拖入一個 Tab Bar Controller
,默認自帶兩個標籤頁,選中 Tab Bar Controller
,按住 control
鏈接至 Navigation Controller
,選擇 Relationship Segue
下的 view controllers
。選中 Tab Bar
中的標籤圖標,移動下標籤順序,拖動便可,將咱們要展現的表格放在前面。選中 Navigation Controller
中的 Item
,修改 title
爲「書籍列表」。設置下每一個標籤的圖標。最後選中 Tab Bar Controller
,勾選 Is Initial View Controller
。如今效果以下:函數
9.如今來作點代碼工做。打開 MainTableViewController
,添加編輯按鈕的 IBOutlet
、添加初始數據、完善數據源方法等,代碼以下:工具
// MARK: 1.--@IBOutlet屬性定義-----------👇
@IBOutlet weak var editButton: UIBarButtonItem!
// MARK: 2.--實例屬性定義----------------👇
var bookList = [
["name": "讀庫","author": "張立憲", "press": "新星出版社"],
["name": "三體","author": "劉慈欣", "press": "重慶出版社"],
["name": "驅魔","author": "韓鬆", "press": "上海文藝出版社"],
["name": "葉曼拈花","author": "葉曼", "press": "中央編譯出版社"],
["name": "南華錄 : 晚明南方士人生活史","author": "趙柏田", "press": "北京大學出版社"],
["name": "青鳥故事集","author": "李敬澤", "press": "譯林出版社"],
["name": "可愛的文化人","author": "俞曉羣", "press": "嶽麓書社"],
["name": "呼吸 : 音樂就在咱們的身體裏","author": "楊照", "press": "廣西師範大學出版社"],
["name": "書生活","author": "馬慧元", "press": "中華書局"],
["name": "葉彌六短篇","author": "葉彌", "press": "海豚出版社"],
["name": "美哉少年","author": "葉彌", "press": "江蘇鳳凰文藝出版社"],
["name": "新與舊","author": "沈從文", "press": "重慶大學出版社"],
["name": "銀河帝國:基地","author": "艾薩克·阿西莫夫", "press": "江蘇文藝出版社"],
["name": "世界上的另外一個你","author": "朗·霍爾 丹佛·摩爾", "press": "湖南文藝出版社"],
["name": "奇島","author": "林語堂", "press": "羣言出版社"]
]
// MARK: 3.--視圖生命週期----------------👇
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true // 容許編輯模式下多選
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: 4.--處理主邏輯-----------------👇
/// 切換表格的編輯與瀏覽狀態
func switchEditMode() {
if tableView.isEditing {
self.setEditing(false, animated: true) // 結束編輯模式
editButton.title = "編輯"
} else {
self.setEditing(true, animated: true) // 進入編輯模式
editButton.title = "取消"
}
}
// MARK: 5.--輔助函數-------------------👇
// MARK: 6.--動做響應-------------------👇
@IBAction func editButtonTapped(_ sender: Any) {
switchEditMode()
}
// MARK: 7.--事件響應-------------------👇
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
// 編輯模式下禁止觸發 segue
if tableView.isEditing {
return false
} else {
return true
}
}
// MARK: 8.--數據源方法------------------👇
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return bookList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell
{
let cell = tableView.dequeueReusableCell(
withIdentifier: "bookCell", for: indexPath)
let row = indexPath.row
cell.textLabel?.text = bookList[row]["name"]
cell.detailTextLabel?.text = bookList[row]["author"]
return cell
}複製代碼
10.如今就能夠看到瀏覽狀態和編輯多選狀態兩種效果:佈局
11.再完善一下書籍詳情頁,打開 DetailTableViewController
,添加代碼以下:
// MARK: 1.--@IBOutlet屬性定義-----------👇
// MARK: 2.--實例屬性定義----------------👇
var bookDetail = ["name": "","author": "", "press": ""]
// MARK: 3.--視圖生命週期----------------👇
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: 4.--處理主邏輯-----------------👇
// MARK: 5.--輔助函數-------------------👇
// MARK: 6.--動做響應-------------------👇
// MARK: 7.--事件響應-------------------👇
// MARK: 8.--數據源方法------------------👇
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell
{
let cell = super.tableView(tableView, cellForRowAt: indexPath)
let row = indexPath.row
switch row {
case 0:
cell.detailTextLabel?.text = bookDetail["name"]
case 1:
cell.detailTextLabel?.text = bookDetail["author"]
case 2:
cell.detailTextLabel?.text = bookDetail["press"]
default:
break
}
return cell
}
// MARK: 9.--視圖代理方法----------------👇複製代碼
12.回到 MainTableViewController
,補充一下 prepare
方法:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let detailVC = segue.destination as! DetailTableViewController
let cell = sender as! UITableViewCell
let selectedIndexPath = tableView.indexPath(for: cell)!
detailVC.bookDetail = bookList[selectedIndexPath.row]
}複製代碼
13.如今詳情頁也能看到內容了:
14.接下來咱們但願在編輯書籍列表的時候,頁面底部顯示工具欄,方便咱們進行刪除等操做。Navigation Controller
是自帶 Toolbar 的,只須要選中它並勾選 Shows Toolbar
就行,可是默認效果實在是不友好,Toolbar 和 Tabbar 緊挨着,既佔空間又不美觀,這也是我要寫這篇文章的主要緣由:
15.所以咱們接下來要嘗試,在編輯時隱藏 Tabbar,只顯示 ToolBar,在結束編輯時,隱藏 ToolBar,從新顯示 Tabbar。其實,另外一個系統自帶的 App 已經實現了這個效果,就是照片 App,效果看下圖。可是仔細看它也有一個問題,它在點擊「選擇」按鈕時,顯示的工具欄是 UIToolbar
類型的,它的高度比 Tabbar 要矮一點,這樣在切換時感受不協調(除了這個問題,iOS 11 上的照片 App 還有其餘問題)。
16.因此咱們打算自定義 Toolbar,且要知足如下幾個特性:
是否是以爲咱們要復刻 Tabbar 了?看起來還真有點像,不過咱們會作的稍微簡單點,看完本文還有想法的能夠再去打磨一下。
問題是咱們怎麼能作的這麼像呢?這要多謝 Xcode 的 View Debugging
功能,能夠把 Tabbar 刨開來看個夠。
17.接下來打開 Xcode,運行一下工程,打開菜單欄:Debug
-> View Debugging
-> Capture View Hierarchy
,能夠把 App 視圖層次屬性看的清清楚楚:
18.下面咱們來逐個實現 Toolbar 須要的特性,先從毛玻璃效果開始。
UIVIew
的子類 ToolBarView.swift
,再建立一個 View 的 xib 文件 ToolBarView.xib
。Custom Class
設置爲 ToolBarView
(這裏不在 File's Owner 裏設置,不少問答的回覆裏亂用 File's Owner ,下次專題講解自定義 UIView 的問題),在 Simulated Metrics
的 Size 項中選擇 Freeform
,再在尺寸設置中將 View 高度改成 49。Visual Effect Views With Blur
,拖入 View 中,設置約束和 View 保持相同高寬、左上對齊。選中 Visual Effect View
,找到設置項 Blur Style
,選擇 Extra Light
。19.通過前面的觀察,Tabbar 上邊沿的細橫線,實際上是一個高度爲 0.3三、帶有背景色的空 Image View,用法是否是很特別。接着咱們在 UI 庫中找到 Image View
,放在 Visual Effect View
上層,並設置約束,高度的約束單獨設置爲0.33(高度直接在 View 尺寸中設置是不起做用的),其餘約束相同。找到 Image View
的 Background
屬性,設置爲黑色加 30% 透明度。
20.再拖拽一個 Button 到 ToolBarView
上,將約束設置爲上下左右居中便可,標題設置爲:「刪除」。到這一步爲止,你應該在 xib 上看到如下層次結構:
21.打開 ToolBarView.swift
,在 Xcode 中建立一個 IBOutlet 關聯至「刪除」按鈕,並添加如下代碼:
class ToolBarView: UIView {
@IBOutlet weak var deleteButton: UIButton!
class func initView() -> ToolBarView {
let myClassNib = UINib(nibName: "ToolBarView", bundle: nil)
let toolBarView = myClassNib.instantiate(
withOwner: nil,
options: nil)[0] as! ToolBarView
return toolBarView
}
}複製代碼
22.打開 MainTableViewController.swift
,添實例屬性:
/// 工具欄視圖
var toolBarView: ToolBarView?
/// 編輯狀態下選中的書籍數組
var selectedBooksIndexs: [Int] {
guard let indexPaths = tableView.indexPathsForSelectedRows else {
return []
}
var indexs: [Int] = []
for indexPath in indexPaths {
indexs.append(indexPath.row)
}
return indexs
}複製代碼
修改 viewDidLoad()
方法以下:
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true // 容許編輯模式下多選
initialToolBar() // 初始化工具欄
}複製代碼
添加方法 initialToolBar()
:
/// 初始化工具欄
func initialToolBar() {
toolBarView = ToolBarView.initView() // 初始化工具欄對象
setupToolBarFrame() // 對工具欄進行佈局
// 添加至 TabBar 視圖中
self.tabBarController?.view.addSubview(toolBarView!)
toolBarView?.isHidden = true // 默認隱藏
registerToolBarButtonAction() // 註冊按鈕點擊事件
}複製代碼
添加方法 setupToolBarFrame()
:
/// 對工具欄進行佈局
func setupToolBarFrame() {
var frame = CGRect()
// 工具欄佈局與 Tabbar 保持一致
frame.origin = (self.tabBarController?.tabBar.frame.origin)!
frame.size = (self.tabBarController?.tabBar.frame.size)!
toolBarView?.frame = frame
}複製代碼
添加方法 registerToolBarButtonAction()
:
/// 註冊工具欄按鈕點擊事件
func registerToolBarButtonAction() {
// 刪除按鈕
toolBarView?.deleteButton.addTarget(
self, action: #selector(self.deleteToolBarButtonTapped(_:)),
for: .touchUpInside)
}複製代碼
添加方法 deleteToolBarButtonTapped(:)
:
/// 響應工具欄刪除按鈕點擊
@objc func deleteToolBarButtonTapped(_ sender: UIButton) {
deleteSelectedBooks() // 刪除選擇的書籍
}複製代碼
添加方法 deleteSelectedBooks()
:
/// 刪除選擇的書籍
func deleteSelectedBooks() {
let indexs = selectedBooksIndexs.sorted()
for index in Array(indexs.reversed()) {
bookList.remove(at: index)
}
tableView.beginUpdates()
tableView.deleteRows(at: indexs.map { IndexPath(row: $0, section: 0) } ,
with: .fade)
tableView.endUpdates()
switchEditMode()
}複製代碼
完善方法 switchEditMode()
:
/// 切換表格的編輯與瀏覽狀態
func switchEditMode() {
if tableView.isEditing {
self.setEditing(false, animated: true) // 結束編輯模式
editButton.title = "編輯"
} else {
self.setEditing(true, animated: true) // 進入編輯模式
editButton.title = "取消"
}
switchToolBarAndTabbar() // 切換顯示工具欄
}複製代碼
添加方法 switchToolBarAndTabbar()
:
/// 切換顯示工具欄
func switchToolBarAndTabbar() {
if tableView.isEditing {
self.tabBarController?.tabBar.isHidden = true // 隱藏 Tab 欄
toolBarView?.isHidden = false // 顯示工具欄
} else {
self.tabBarController?.tabBar.isHidden = false // 顯示 Tab 欄
toolBarView?.isHidden = true // 隱藏工具欄
}
}複製代碼
23.在 Xcode 中運行一下工程,如今就能夠愉快地展現自定義 Toolbar 和刪除操做了:
24.最後再解決一個小問題,設備旋轉時須要對工具欄進行從新佈局,修改 viewDidLoad()
方法:
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true // 容許編輯模式下多選
initialToolBar() // 初始化工具欄
addObserver() // 註冊須要監聽的對象
}複製代碼
添加方法 addObserver()
:
/// 註冊須要監聽的事件
func addObserver() {
// 監聽設備旋轉事件
NotificationCenter.default.addObserver(
self,
selector: #selector(self.updateLayoutWhenOrientationChanged),
name: NSNotification.Name.UIDeviceOrientationDidChange,
object: nil)
}複製代碼
添加方法 updateLayoutWhenOrientationChanged()
:
/// 設備旋轉時從新佈局
@objc func updateLayoutWhenOrientationChanged() {
setupToolBarFrame() // 對工具欄進行佈局
}複製代碼
25.如今再看是否是很棒!咱們這篇教程到這裏就結束了,謝謝你們的耐心閱讀!
後記:寫這篇教程花了三天時間,我沒有預計到竟然這麼漫長。其實當時解決問題寫代碼的時間很快,只要一個小時左右。寫教程不像調試代碼,它須要在邏輯上一鼓作氣,所以先後不斷的更換截圖、更新代碼,並且示例雖然簡單,但要儘可能作到合理封裝、邏輯清晰,在代碼規範上也是一個必要的示範。我還會繼續堅持寫教程,相信之後會越寫越快、越寫越清晰。你們有什麼建議隨時提啊,個人郵箱: pmtnmd@163.com 。
歡迎訪問 個人我的網站 ,閱讀更多文章。