版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2019.05.16 星期四 |
前言
iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面辐宾,用戶交互也是通過UIKit進(jìn)行的狱从。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用叠纹、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用季研、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)
開始
首先看下寫作環(huán)境
Swift 4.2, iOS 12, Xcode 10
許多iOS應(yīng)用程序需要一個(gè)菜單來在視圖之間導(dǎo)航或讓用戶做出選擇。 一種常用的設(shè)計(jì)是側(cè)面菜單誉察。
您可以使用簡(jiǎn)單的表單輕松制作側(cè)邊菜單与涡,但是如何在UI中引入一些樂趣呢? 你想在用戶的臉上露出微笑持偏,并一次又一次地將它們帶回你的應(yīng)用程序驼卖。 實(shí)現(xiàn)此目的的一種方法是創(chuàng)建3D側(cè)邊欄動(dòng)畫。
在本教程中鸿秆,您將學(xué)習(xí)如何通過操縱CALayer屬性來創(chuàng)建3D側(cè)邊欄動(dòng)畫來為一些UIView元素設(shè)置動(dòng)畫款慨。 這個(gè)動(dòng)畫的靈感來自一個(gè)名為Taasky
的To-Do
應(yīng)用程序。
在本教程中谬莹,您將使用以下元素:
Storyboards
Auto Layout constraints
UIScrollView
View controller containment
Core Animation
打開名為TaskChooser
的入門項(xiàng)目。
想象一下,您正在創(chuàng)建一個(gè)與您的同事或朋友談判活動(dòng)的基本應(yīng)用程序附帽。如果你在里面豎起大拇指埠戳,如果你不能成功,請(qǐng)大拇指向下蕉扮。你甚至可以因天氣惡劣而下降整胃。
花點(diǎn)時(shí)間看一下這個(gè)項(xiàng)目。你會(huì)看到它是一個(gè)標(biāo)準(zhǔn)的Xcode Master-Detail
模板應(yīng)用程序喳钟,它顯示了一個(gè)圖像表屁使。
-
MenuViewController:一個(gè)
UITableViewController
,它使用自定義表格視圖單元MenuItemCell
來設(shè)置每個(gè)單元格的背景顏色奔则。它還有一個(gè)圖像蛮寂。 -
MenuDataSource:實(shí)現(xiàn)
UITableViewDataSource
以從MenuItems.json
提供表數(shù)據(jù)的對(duì)象。這些數(shù)據(jù)可能來自生產(chǎn)情況下的服務(wù)器易茬。 - DetailViewController:使用與您選擇的單元格相同的背景顏色顯示大圖像酬蹋。
構(gòu)建并運(yùn)行應(yīng)用程序。您應(yīng)該看到啟動(dòng)項(xiàng)目加載了7行顏色和圖標(biāo):
使用菜單顯示您選擇的選項(xiàng):
這是功能性的抽莱,但外觀和感覺相當(dāng)普通范抓。 你希望你的應(yīng)用程序既令人驚喜又高興!
在本教程中食铐,您將把Master-Detail
應(yīng)用程序重構(gòu)為水平滾動(dòng)視圖匕垫。 您將在容器視圖中嵌入master
和 detail
視圖。
接下來虐呻,您將添加一個(gè)按鈕來顯示或隱藏菜單象泵。 然后,您將在菜單上添加整齊的3D折疊效果铃慷。
作為此3D動(dòng)畫側(cè)邊欄的最后一步单芜,您將同步旋轉(zhuǎn)菜單按鈕以顯示或隱藏菜單。
您的第一個(gè)任務(wù)是將MenuViewController
和DetailViewController
轉(zhuǎn)換為滑出側(cè)邊欄犁柜,其中滾動(dòng)視圖包含菜單和詳細(xì)視圖并排洲鸠。
Restructuring Your Storyboard
在重建菜單之前,您需要進(jìn)行一些拆卸馋缅。
在Project
導(dǎo)航器的Views
文件夾中打開Main.storyboard
扒腕。 你可以看到由segues
連接的UINavigationController
,MenuViewController
和DetailViewController
:
1. Deleting the Old Structure
導(dǎo)航控制器場(chǎng)景(Navigation Controller Scene)
不會(huì)激發(fā)快樂萤悴。 選擇該場(chǎng)景并將其刪除瘾腰。 接下來,選擇MenuViewController
和DetailViewController
之間的segue
并刪除它覆履。
完成后蹋盆,開始工作费薄。
2. Adding a New Root Container
由于UINavigationController
消失了,您不再擁有項(xiàng)目中視圖控制器的頂級(jí)容器栖雾。 你現(xiàn)在就加一個(gè)楞抡。
在Project
導(dǎo)航器中選擇Views
文件夾。 按Command-N
將新文件添加到項(xiàng)目中析藕。 然后:
- 1) 選擇
iOS?CocoaTouch Class
召廷。 點(diǎn)擊Next
。 - 2) 將類命名為
RootViewController
账胧。 - 3) 確保
RootViewController
是UIViewController
的子類竞慢。
- 4) 確保未選中
Also create XIB file
。 - 5) 語(yǔ)言應(yīng)該是
Swift
治泥。
再次打開Main.storyboard
筹煮。
使用快捷鍵Command-Shift-L
打開對(duì)象庫(kù),并將UIViewController
的實(shí)例拖到故事板车摄。
從對(duì)象層次結(jié)構(gòu)中選擇View Controller Scene
寺谤,然后打開Identity inspector
。 將Class
區(qū)域設(shè)置為RootViewController
吮播。
接下來变屁,打開Attributes inspector
,然后選中Is Initial View Controller
框意狠。
3. Adding Identifiers to View Controllers
由于MenuViewController
和DetailViewController
不再通過segues
連接粟关,因此您需要一種從代碼中訪問它們的方法。 因此环戈,您的下一步是提供一些標(biāo)識(shí)符來執(zhí)行此操作闷板。
從對(duì)象層次結(jié)構(gòu)中選擇Menu View Controller Scene
。 打開Identity inspector
并將Storyboard ID
設(shè)置為MenuViewController
院塞。
這個(gè)字符串可以是任何合理的值遮晚,但一個(gè)易于記憶的技術(shù)是使用類的名稱。
接下來拦止,從Object
層次結(jié)構(gòu)中選擇Detail View Controller Scene
并執(zhí)行相同的操作县遣。 將Storyboard ID
設(shè)置為DetailViewController
。
這就是你需要在Main.storyboard
中做的所有事情汹族。 本教程的其余部分將在代碼中萧求。
Creating Contained View Controllers
在本節(jié)中,您將創(chuàng)建一個(gè)UIScrollView
并向該滾動(dòng)視圖添加兩個(gè)容器顶瞒。 容器將保存MenuViewController
和DetailViewController
夸政。
1. Creating a Scroll View
您的第一步是創(chuàng)建UIScrollView
。
在Project
導(dǎo)航器中打開RootViewController.swift
榴徐。 刪除Xcode從RootViewController
內(nèi)部提供的所有內(nèi)容守问。
在RootViewController
上面添加此擴(kuò)展:
extension UIView {
func embedInsideSafeArea(_ subview: UIView) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor)
.isActive = true
subview.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)
.isActive = true
subview.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor)
.isActive = true
subview.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
.isActive = true
}
}
這是一個(gè)幫助方法匀归,您將在本教程中使用幾次。 代碼將傳入的視圖添加為子視圖酪碘,然后添加四個(gè)約束以將子視圖粘貼到其自身內(nèi)朋譬。
接下來在文件末尾添加此擴(kuò)展名:
extension RootViewController: UIScrollViewDelegate {
}
您將需要監(jiān)聽UIScrollView
以進(jìn)行更改。 該操作稍后在本教程中進(jìn)行兴垦,因此此擴(kuò)展目前為空。
最后字柠,在RootViewController
中插入以下代碼:
// 1
lazy var scroller: UIScrollView = {
let scroller = UIScrollView(frame: .zero)
scroller.isPagingEnabled = true
scroller.delaysContentTouches = false
scroller.bounces = false
scroller.showsHorizontalScrollIndicator = false
scroller.delegate = self
return scroller
}()
// 2
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(named: "rw-dark")
view.embedInsideSafeArea(scroller)
}
// 3
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
以下是您在此代碼中所做的事情:
- 1) 首先探越,創(chuàng)建一個(gè)
UIScrollView
。 您希望啟用分頁(yè)窑业,以便內(nèi)容在滾動(dòng)視圖內(nèi)以原子單位移動(dòng)钦幔。 您已禁用delayedContentTouches
,以便內(nèi)部控制器能夠快速響應(yīng)用戶觸摸常柄。bounces
設(shè)置為false
鲤氢,因此您不會(huì)從滾動(dòng)條獲得彈性感。 然后西潘,將RootViewController
設(shè)置為scroll view
的代理卷玉。 - 2) 在
viewDidLoad()
中,您可以使用之前添加的幫助方法設(shè)置背景顏色并將scroll view
嵌入根視圖中喷市。 - 3) 對(duì)
preferredStatusBarStyle
的覆蓋允許狀態(tài)欄在深色背景上顯示為淺色相种。
構(gòu)建并運(yùn)行您的應(yīng)用程序,以顯示它在此重構(gòu)后正確啟動(dòng):
由于您尚未將按鈕和內(nèi)容添加到新的RootViewController
品姓,因此您應(yīng)該只能看到已設(shè)置的深色背景沸移。 別擔(dān)心章贞,您將在下一節(jié)中將它們添加回來。
2. Creating Containers
現(xiàn)在,您將創(chuàng)建UIView
實(shí)例醋旦,它們將充當(dāng)MenuViewController
和DetailViewController
的容器。 然后阻桅,您將它們添加到scroll view
翔烁。
在RootViewController
的頂部添加這些屬性:
let menuWidth: CGFloat = 80.0
var menuContainer = UIView(frame: .zero)
var detailContainer = UIView(frame: .zero)
接下來,將此方法添加到RootViewController
:
func installMenuContainer() {
// 1
scroller.addSubview(menuContainer)
menuContainer.translatesAutoresizingMaskIntoConstraints = false
menuContainer.backgroundColor = .orange
// 2
menuContainer.leadingAnchor.constraint(equalTo: scroller.leadingAnchor)
.isActive = true
menuContainer.topAnchor.constraint(equalTo: scroller.topAnchor)
.isActive = true
menuContainer.bottomAnchor.constraint(equalTo: scroller.bottomAnchor)
.isActive = true
// 3
menuContainer.widthAnchor.constraint(equalToConstant: menuWidth)
.isActive = true
menuContainer.heightAnchor.constraint(equalTo: scroller.heightAnchor)
.isActive = true
}
以下是您使用此代碼所做的事情:
- 1) 添加
menuContainer
作為scroller
的子視圖惧互,并為其添加臨時(shí)顏色哎媚。 在開發(fā)過程中使用非品牌顏色是了解開發(fā)過程中工作進(jìn)展的好方法。 - 2) 接下來喊儡,將
menuContainer
的頂部和底部固定到scroll view
的相同邊緣拨与。 - 3) 最后,將
width
設(shè)置為80.0
的常量值艾猜,并將容器的高度固定為scroll view
的高度买喧。
接下來捻悯,將以下方法添加到RootViewController
:
func installDetailContainer() {
//1
scroller.addSubview(detailContainer)
detailContainer.translatesAutoresizingMaskIntoConstraints = false
detailContainer.backgroundColor = .red
//2
detailContainer.trailingAnchor.constraint(equalTo: scroller.trailingAnchor)
.isActive = true
detailContainer.topAnchor.constraint(equalTo: scroller.topAnchor)
.isActive = true
detailContainer.bottomAnchor.constraint(equalTo: scroller.bottomAnchor)
.isActive = true
//3
detailContainer.leadingAnchor
.constraint(equalTo: menuContainer.trailingAnchor)
.isActive = true
detailContainer.widthAnchor.constraint(equalTo: scroller.widthAnchor)
.isActive = true
}
- 1) 與
installMenuContainer
類似,您將detailContainer
作為子視圖添加到scroll view
淤毛。 - 2) 頂部今缚,底部和右側(cè)邊緣固定到它們各自的
scroll view
邊緣。detailContainer
的leading edge
連接到menuContainer
低淡。 - 3) 最后姓言,容器的寬度始終與
scroll view
的寬度相同。
要讓UIScrollView
滾動(dòng)其內(nèi)容蔗蹋,它需要知道該內(nèi)容有多大何荚。 您可以通過使用UIScrollView
的contentSize
屬性或隱式定義內(nèi)容的大小來實(shí)現(xiàn)。
在這種情況下猪杭,內(nèi)容大小由五件事隱式定義:
- 1)
menu container
高度==scroll view
高度 - 2)
detail container
的后緣固定到menu container
的前緣 - 3)
menu container
的寬度==80
- 4)
detail container
的寬度==scroll view
的寬度 - 5) 外部
detail and menu container
的邊緣錨定到scroller
的邊緣
最后要做的是使用這兩種方法餐塘。 在viewDidLoad()
的末尾添加這些行:
installMenuContainer()
installDetailContainer()
構(gòu)建并運(yùn)行您的應(yīng)用程序,看看一些糖果色的奇跡皂吮。 您可以拖動(dòng)內(nèi)容以隱藏橙色菜單容器戒傻。 您已經(jīng)可以看到成品開始形成。
3. Adding Contained View Controllers
您正在構(gòu)建創(chuàng)建界面所需的視圖堆棧蜂筹。 下一步是在您創(chuàng)建的容器中安裝MenuViewController
和DetailViewController
需纳。
你仍然想要一個(gè)導(dǎo)航欄,因?yàn)槟阆胍粋€(gè)放置菜單顯示按鈕的地方狂票。 將此擴(kuò)展添加到RootViewController.swift
的末尾:
extension RootViewController {
func installInNavigationController(_ rootController: UIViewController)
-> UINavigationController {
let nav = UINavigationController(rootViewController: rootController)
//1
nav.navigationBar.barTintColor = UIColor(named: "rw-dark")
nav.navigationBar.tintColor = UIColor(named: "rw-light")
nav.navigationBar.isTranslucent = false
nav.navigationBar.clipsToBounds = true
//2
addChild(nav)
return nav
}
}
以下是此代碼中發(fā)生的情況:
- 1) 此方法采用視圖控制器候齿,將其安裝在
UINavigationController
中,然后設(shè)置導(dǎo)航欄的視覺樣式闺属。 - 2) 視圖控制器包含的最重要部分是
addChild(nav)
慌盯。 這會(huì)將UINavigationController
安裝為RootViewController
的子視圖控制器。 這意味著在iPad
上旋轉(zhuǎn)或拆分視圖導(dǎo)致的特征變化等事件可以在層次結(jié)構(gòu)中向下傳播給子節(jié)點(diǎn)掂器。
接下來亚皂,在installInNavigationController(_ :)
之后將此方法添加到同一擴(kuò)展中以幫助安裝MenuViewController
和DetailViewController
:
func installFromStoryboard(_ identifier: String,
into container: UIView)
-> UIViewController {
guard let viewController = storyboard?
.instantiateViewController(withIdentifier: identifier) else {
fatalError("broken storyboard expected \(identifier) to be available")
}
let nav = installInNavigationController(viewController)
container.embedInsideSafeArea(nav.view)
return viewController
}
此方法從故事板中實(shí)例化視圖控制器,警告開發(fā)人員故事板中斷国瓮。
然后灭必,代碼將視圖控制器放在UINavigationController
中,并將該導(dǎo)航控制器嵌入到容器中乃摹。
接下來禁漓,在主類中添加這些屬性以跟蹤MenuViewController
和DetailViewController
:
var menuViewController: MenuViewController?
var detailViewController: DetailViewController?
然后在viewDidLoad()
的末尾插入這些行:
menuViewController =
installFromStoryboard("MenuViewController",
into: menuContainer) as? MenuViewController
detailViewController =
installFromStoryboard("DetailViewController",
into: detailContainer) as? DetailViewController
在這個(gè)片段中,您實(shí)例化了MenuViewController
和DetailViewController
并保留了對(duì)它們的引用孵睬,因?yàn)樯院竽鷮⑿枰鼈儭?/p>
構(gòu)建并運(yùn)行應(yīng)用程序播歼,您將看到菜單可見,雖然比以前更瘦掰读。
這些按鈕不會(huì)導(dǎo)致DetailViewController
更新秘狞,因?yàn)?code>segue不再存在叭莫。 你將在下一節(jié)中解決這個(gè)問題。
您已完成本教程的視圖包含部分烁试。 現(xiàn)在你可以進(jìn)入真正有趣的東西了雇初。
Reconnect Menu and Detail Views
在你進(jìn)行拆除橫沖直撞之前,在MenuViewController
中選擇一個(gè)表格單元格會(huì)觸發(fā)一個(gè)segue
减响,它將選定的MenuItem
傳遞給DetailViewController
靖诗。
它很便宜而且完成了工作,但是有一個(gè)小問題辩蛋。 該模式需要MenuViewController
了解DetailViewController
呻畸。
這意味著MenuViewController
與DetailViewController
緊密綁定。 如果您不再想使用DetailViewController
來顯示菜單選項(xiàng)的結(jié)果悼院,會(huì)發(fā)生什么?
作為優(yōu)秀的開發(fā)人員咒循,您應(yīng)該尋求減少系統(tǒng)中的緊密綁定量据途。 您現(xiàn)在將設(shè)置一個(gè)新模式。
1. Creating a Delegate Protocol
首先要做的是在MenuViewController
中創(chuàng)建一個(gè)委托協(xié)議叙甸,它允許您傳達(dá)菜單選擇更改颖医。
在Project
導(dǎo)航器中找到MenuViewController.swift
并打開該文件。
由于您不再使用segue
裆蒸,您可以繼續(xù)刪除prepare(for:sender :)
熔萧。
接下來,在MenuViewController
類聲明之上添加此協(xié)議定義:
protocol MenuDelegate: class {
func didSelectMenuItem(_ item: MenuItem)
}
接下來僚祷,在MenuViewController
的主體中插入以下代碼:
//1
weak var delegate: MenuDelegate?
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
//2
let item = datasource.menuItems[indexPath.row]
delegate?.didSelectMenuItem(item)
//3
DispatchQueue.main.async {
tableView.deselectRow(at: indexPath, animated: true)
}
}
這是代碼的作用:
- 1) 在第一個(gè)代碼片段中佛致,您聲明了一個(gè)感興趣的各方可以采用的協(xié)議。 在
MenuViewController
中辙谜,您聲明了一個(gè)weak delegate
屬性俺榆。 在協(xié)議引用中使用weak
有助于避免創(chuàng)建保留周期。 - 2) 接下來装哆,實(shí)現(xiàn)
UITableViewDelegate
方法tableView(_:didSelectRowAt :)
將選定的MenuItem
傳遞給委托罐脊。 - 3) 最后一個(gè)聲明是取消選擇單元格并刪除其突出顯示的整體操作。
2. Implementing the MenuDelegate Protocol
您現(xiàn)在可以實(shí)現(xiàn)您創(chuàng)建的協(xié)議蜕琴,以將選擇更改發(fā)送到DetailViewController
萍桌。
打開RootViewController.swift
并將此擴(kuò)展名添加到文件末尾:
extension RootViewController: MenuDelegate {
func didSelectMenuItem(_ item: MenuItem) {
detailViewController?.menuItem = item
}
}
此代碼聲明RootViewController
采用MenuDelegate
。 當(dāng)您選擇一個(gè)菜單項(xiàng)時(shí)凌简,RootViewController
通過將選定的MenuItem
傳遞給實(shí)例來告訴DetailViewController
該更改上炎。
最后,在viewDidLoad()
的末尾插入此行:
menuViewController?.delegate = self
這告訴MenuViewController
RootViewController
是委托号醉。
構(gòu)建并運(yùn)行應(yīng)用程序反症。 您的菜單選擇現(xiàn)在將更改DetailViewController
的內(nèi)容辛块。 豎起大拇指。
Controlling the Scroll View
到現(xiàn)在為止還挺好铅碍。 你的菜單工作润绵,應(yīng)用程序看起來更好。
但是胞谈,您還會(huì)注意到手動(dòng)滾動(dòng)菜單不會(huì)持續(xù)很長(zhǎng)時(shí)間尘盼。 菜單總是反彈回視圖。
滾動(dòng)視圖屬性isPagingEnabled
會(huì)導(dǎo)致該效果烦绳,因?yàn)槟褜⑵湓O(shè)置為true
卿捎。 你現(xiàn)在就解決這個(gè)問題。
仍然在RootViewController
中工作径密,在下面添加以下行:menuWidth:CGFloat = 80.0
:
lazy var threshold = menuWidth/2.0
在這里午阵,您可以選擇一個(gè)任意點(diǎn),菜單將選擇隱藏或顯示自己享扔。 你使用lazy
底桂,因?yàn)槟阏谟?jì)算相對(duì)于menuWidth
的值。
在RootViewController
中找到extension RootViewController: UIScrollViewDelegate
并在擴(kuò)展中插入此代碼:
//1
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
scrollView.isPagingEnabled = offset.x < threshold
}
//2
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool) {
let offset = scrollView.contentOffset
if offset.x > threshold {
hideMenu()
}
}
//3
func moveMenu(nextPosition: CGFloat) {
let nextOffset = CGPoint(x: nextPosition, y: 0)
scroller.setContentOffset(nextOffset, animated: true)
}
func hideMenu() {
moveMenu(nextPosition: menuWidth)
}
func showMenu() {
moveMenu(nextPosition: 0)
}
func toggleMenu() {
let menuIsHidden = scroller.contentOffset.x > threshold
if menuIsHidden {
showMenu()
} else {
hideMenu()
}
}
看看這段代碼的作用:
- 1) 第一個(gè)
UIScrollViewDelegate
方法惧眠,scrollViewDidScroll(_ :)
籽懦,非常有用。 它總是會(huì)告訴您何時(shí)更改了scroll view
的contentOffset
氛魁。 您可以根據(jù)水平偏移量是否高于閾值threshold
來設(shè)置isPagingEnabled
暮顺。 - 2) 接下來,實(shí)現(xiàn)
scrollViewDidEndDragging(_:willDecelerate :)
以檢測(cè)滾動(dòng)視圖上的抬起觸摸秀存。 只要內(nèi)容偏移量大于閾值捶码,就隱藏菜單;否則分頁(yè)效果將保持并顯示菜單应又。 - 3) 最后一種方法是幫助菜單將菜單設(shè)置到位置:顯示宙项,隱藏和切換。
構(gòu)建并運(yùn)行您的應(yīng)用程序株扛。 現(xiàn)在尤筐,嘗試拖動(dòng)scroll view
,看看會(huì)發(fā)生什么洞就。 越過閾值時(shí)盆繁,菜單會(huì)彈出或關(guān)閉:
Adding a Menu Button
在本節(jié)中,您將向?qū)Ш綑谔砑訚h堡(burger)
按鈕旬蟋,這樣您的用戶就不必拖動(dòng)來顯示和隱藏菜單油昂。
因?yàn)槟肷院鬄榇税粹o設(shè)置動(dòng)畫,所以這需要是UIView
而不是基于圖像的UIBarButton
。
1. Creating a Hamburger View
在Project
導(dǎo)航器中選擇Views
文件夾冕碟,然后添加一個(gè)新的Swift文件拦惋。
- 1) 選擇
iOS?CocoaTouch Class
。 點(diǎn)擊Next
安寺。 - 2) 將類命名為
HamburgerView
厕妖。 - 3) 確保
HamburgerView
是UIView
的子類。 - 4) 語(yǔ)言應(yīng)該是
Swift
挑庶。
打開HamburgerView.swift
并使用以下代碼替換HamburgerView
類中的所有內(nèi)容:
//1
let imageView: UIImageView = {
let view = UIImageView(image: UIImage(imageLiteralResourceName: "Hamburger"))
view.contentMode = .center
return view
}()
//2
required override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
addSubview(imageView)
}
這是你在這里做的事情:
- 1) 首先言秸,使用庫(kù)中的資源創(chuàng)建
UIImageView
。 - 2) 然后添加該圖像視圖迎捺,為兩種可能的
init
方法創(chuàng)建路徑举畸。
2. Installing the Hamburger View
現(xiàn)在您有了一個(gè)視圖,您可以將其安裝在屬于DetailViewController
的導(dǎo)航欄中凳枝。
再次打開RootViewController.swift
并在主RootViewController
類的頂部插入此屬性:
var hamburgerView: HamburgerView?
接下來將此擴(kuò)展名附加到文件末尾:
extension RootViewController {
func installBurger(in viewController: UIViewController) {
let action = #selector(burgerTapped(_:))
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: action)
let burger = HamburgerView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
burger.addGestureRecognizer(tapGestureRecognizer)
viewController.navigationItem.leftBarButtonItem
= UIBarButtonItem(customView: burger)
hamburgerView = burger
}
@objc func burgerTapped(_ sender: Any) {
toggleMenu()
}
}
最后將此語(yǔ)句添加到viewDidLoad()
的底部:
if let detailViewController = detailViewController {
installBurger(in: detailViewController)
}
這組代碼為漢堡按鈕提供了一個(gè)實(shí)例變量抄沮,因?yàn)槟芸炀拖胍獮樗O(shè)置動(dòng)畫。然后岖瑰,您可以創(chuàng)建一個(gè)方法合是,以在任何視圖控制器的導(dǎo)航欄中安裝漢堡。
方法installBurger(in :)
在視圖中創(chuàng)建一個(gè)tap
方法锭环,調(diào)用方法burgerTapped(_ :)
。
請(qǐng)注意泊藕,您必須使用@objc
注釋burgerTapped(_ :)
辅辩,因?yàn)槟诖颂幨褂?code>Objective-C運(yùn)行時(shí)。此方法根據(jù)當(dāng)前狀態(tài)切換菜單娃圆。
然后使用此方法在屬于DetailViewController
的UINavigationBar
中安裝按鈕玫锋。從體系結(jié)構(gòu)的角度來看,DetailViewController
不知道這個(gè)按鈕讼呢,也不需要處理任何菜單狀態(tài)操作撩鹿。你保持責(zé)任分離。
就這些悦屏。在構(gòu)建對(duì)象堆棧時(shí)节沦,使3D側(cè)邊欄動(dòng)畫生動(dòng)的步驟變得越來越少。
構(gòu)建并運(yùn)行您的應(yīng)用程序础爬。你會(huì)看到你現(xiàn)在有一個(gè)漢堡按鈕可以切換菜單甫贯。
Adding Perspective to the Menu
為了回顧你到目前為止所做的事情,你已經(jīng)將Master-Detail
應(yīng)用程序重構(gòu)為可行的側(cè)面菜單式應(yīng)用程序看蚜,用戶可以拖動(dòng)或使用按鈕來顯示和隱藏菜單叫搁。
現(xiàn)在,為您的下一步:菜單的動(dòng)畫版本應(yīng)該看起來像一個(gè)面板打開和關(guān)閉。 菜單按鈕將在菜單打開時(shí)順時(shí)針旋轉(zhuǎn)渴逻,在菜單關(guān)閉時(shí)逆時(shí)針旋轉(zhuǎn)疾党。
為此,您將計(jì)算可見的菜單視圖的分?jǐn)?shù)惨奕,然后使用它來計(jì)算菜單的旋轉(zhuǎn)角度雪位。
1. Manipulating the Menu Layer
仍在RootViewController.swift
中,將此擴(kuò)展名添加到文件中:
extension RootViewController {
func transformForFraction(_ fraction: CGFloat, ofWidth width: CGFloat)
-> CATransform3D {
//1
var identity = CATransform3DIdentity
identity.m34 = -1.0 / 1000.0
//2
let angle = -fraction * .pi/2.0
let xOffset = width/2.0 + width * fraction/4.0
//3
let rotateTransform = CATransform3DRotate(identity, angle, 0.0, 1.0, 0.0)
let translateTransform = CATransform3DMakeTranslation(xOffset, 0.0, 0.0)
return CATransform3DConcat(rotateTransform, translateTransform)
}
}
這是transformForFraction(_:ofWidth :)
的逐個(gè)分析:
- 1)
CATransform3DIdentity
是一個(gè)4×4
矩陣墓贿,其中對(duì)角線為1茧泪,其他地方為零。CATransform3DIdentity
的m34
屬性是第3行第4列中的值聋袋,它控制轉(zhuǎn)換中的透視量队伟。 - 2) 角度和偏移量作為輸入
fraction
的函數(shù)計(jì)算。 當(dāng)fraction
為1.0
時(shí)幽勒,菜單將完全隱藏嗜侮,當(dāng)它為0.0
時(shí),菜單將完全可見啥容。 - 3) 計(jì)算最終變換锈颗。
CATransform3DRotate
使用angle
來確定圍繞y軸的旋轉(zhuǎn)量:-90
度使菜單垂直于視圖的背面,0
度渲染菜單與x-y
平面平行咪惠,CATransform3DMakeTranslation
將菜單移動(dòng)到中心的右側(cè)击吱, 和CATransform3DConcat
連接translateTransform
和rotateTransform
,以便菜單在旋轉(zhuǎn)時(shí)顯示為側(cè)滑遥昧。
注意:
m34
值通常計(jì)算為1
除以表示觀察者在z軸上的位置的數(shù)字覆醇,同時(shí)觀察2D x-y
平面。 負(fù)z
值表示觀察者在平面前面炭臭,而正z
值表示觀察者在平面后面永脓。在此
viewer
與平面中對(duì)象邊緣之間繪制線條會(huì)產(chǎn)生3D透視效果。 當(dāng)viewer
移動(dòng)得更遠(yuǎn)時(shí)鞋仍,視角不太明顯常摧。 嘗試更改1,000
到500
或2,000
,以查看菜單的透視圖如何更改威创。
接下來落午,將此擴(kuò)展添加到RootViewController.swift
:
extension RootViewController {
//1
func calculateMenuDisplayFraction(_ scrollview: UIScrollView) -> CGFloat {
let fraction = scrollview.contentOffset.x/menuWidth
let clamped = Swift.min(Swift.max(0, fraction), 1.0)
return clamped
}
//2
func updateViewVisibility(_ container: UIView, fraction: CGFloat) {
container.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)
container.layer.transform = transformForFraction(fraction,
ofWidth: menuWidth)
container.alpha = 1.0 - fraction
}
}
此代碼提供了一些用于打開和關(guān)閉菜單的幫助程序:
- 1)
calculateMenuDisplayFraction(_ :)
將原始水平偏移量轉(zhuǎn)換為相對(duì)于菜單寬度的1.0的fraction
。 該值夾在0.0和1.0之間那婉。 - 2)
updateViewVisibility(_:fraction :)
將分?jǐn)?shù)生成的變換應(yīng)用于視圖層板甘。anchorPoint
是轉(zhuǎn)換應(yīng)用的鉸鏈,因此CGPoint(x:1.0详炬,y:0.5)
表示右手邊緣和垂直中心盐类。
通過設(shè)置alpha
寞奸,隨著轉(zhuǎn)換的進(jìn)行,視圖也會(huì)變暗在跳。
現(xiàn)在枪萄,找到scrollViewDidScroll(_ :)
并在方法的末尾插入這些行:
let fraction = calculateMenuDisplayFraction(scrollView)
updateViewVisibility(menuContainer, fraction: fraction)
構(gòu)建并運(yùn)行應(yīng)用程序。 當(dāng)您向左拖動(dòng)詳細(xì)視圖時(shí)猫妙,菜單現(xiàn)在似乎在細(xì)節(jié)視圖下折疊瓷翻。
Rotating the Burger Button
在本教程中,您要做的最后一件事是在scroll view
移動(dòng)時(shí)使?jié)h堡按鈕看起來在屏幕上滾動(dòng)割坠。
打開HamburgerView.swift
并將此方法插入到類中:
func setFractionOpen(_ fraction: CGFloat) {
let angle = fraction * .pi/2.0
imageView.transform = CGAffineTransform(rotationAngle: angle)
}
此代碼將視圖作為fraction
的函數(shù)旋轉(zhuǎn)齐帚。 當(dāng)菜單完全打開時(shí),視圖旋轉(zhuǎn)90度彼哼。
返回RootViewController.swift
对妄。 找到scrollViewDidScroll(_ :)
并將此行追加到方法的末尾:
hamburgerView?.setFractionOpen(1.0 - fraction)
當(dāng)scroll view
移動(dòng)時(shí),這會(huì)旋轉(zhuǎn)漢堡按鈕敢朱。
然后剪菱,因?yàn)閱?dòng)應(yīng)用程序時(shí)菜單已打開,所以將此行添加到viewDidLoad()
的末尾以將菜單置于正確的初始狀態(tài):
hamburgerView?.setFractionOpen(1.0)
構(gòu)建并運(yùn)行您的應(yīng)用程序拴签。 滑動(dòng)并點(diǎn)按菜單以查看動(dòng)態(tài)和同步的3D側(cè)邊欄動(dòng)畫:
在本教程中孝常,您用到了:
- 視圖控制器。
-
UIScrollView
隱式內(nèi)容大小蚓哩。 - 代理模式构灸。
- 透視隨
CATransform3D
和m34
而變化。
嘗試使用m34
值來查看它對(duì)轉(zhuǎn)換的影響岸梨。 如果您想了解有關(guān)m34
的更多信息冻押,請(qǐng)閱讀this xdPixel blog post。
Wikipedia
的Perspective頁(yè)面有一些很好的照片盛嘿,解釋了視覺透視Perspective
的概念。
另外括袒,請(qǐng)考慮如何在自己的應(yīng)用程序中使用此3D側(cè)邊欄動(dòng)畫次兆,為用戶交互添加一點(diǎn)生命。 令人驚訝的是锹锰,對(duì)菜單這么簡(jiǎn)單的東西的微妙影響可以增加整個(gè)用戶體驗(yàn)芥炭。
后記
本篇主要講述了基于CALayer屬性的一種3D邊欄動(dòng)畫的實(shí)現(xiàn),感興趣的給個(gè)贊或者關(guān)注~~~