未經(jīng)授權(quán)慢洋,禁止轉(zhuǎn)載
原文:http://www.reibang.com/p/01cda53e2fc8
轉(zhuǎn)眼間 WWDC 19 已經(jīng)過(guò)去1個(gè)多月了航邢,這篇文章本應(yīng)該很早就寫(xiě)的愤钾,但是有些代碼 beta1-beta4 一個(gè) beta 變一次 API笛丙,而且之前幾個(gè) beta 部分初始化方法還是以 __ 開(kāi)頭的私有方法(無(wú)力吐槽)嫉鲸,所以拖到現(xiàn)在 beta4 API 基本穩(wěn)定了才開(kāi)始寫(xiě)這篇文章。
目錄
- Gestures
- Presentations
- Search
- Menus
PDF(長(zhǎng)圖)
如果你已經(jīng)升到 iOS 13 你會(huì)發(fā)現(xiàn)當(dāng)你在 Safari 中截圖后有一個(gè) “整頁(yè)” 的功能棵磷,可以把當(dāng)前的 HTML 轉(zhuǎn)成 PDF 存到 “文件” 中蛾狗。那么你可能會(huì)想了,這個(gè)新特性雨窩無(wú)瓜啊泽本,我又不做瀏覽器的 App淘太。其實(shí)我們可以把 scrollView
轉(zhuǎn)成 image
,再把 image
轉(zhuǎn)成 PDF规丽,這樣我們就可以把這個(gè) scrollView
做成一個(gè)長(zhǎng)圖了箕般,我們先來(lái)看下效果第队。
怎么樣是不是挺不錯(cuò)的兵迅,接下來(lái)就讓我們來(lái)看看這是怎么實(shí)現(xiàn)的甜紫。
首先我們要在控制器中實(shí)現(xiàn) UIScreenshotServiceDelegate
代理,由于 iOS 13 項(xiàng)目結(jié)構(gòu)發(fā)生了變化艘狭,這里列出兩種設(shè)置代理的方式挎扰。
// iOS 13項(xiàng)目結(jié)構(gòu)
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.windowScene?.screenshotService?.delegate = self
// iOS13之前項(xiàng)目結(jié)構(gòu)
UIApplication.shared.keyWindow?.windowScene?.screenshotService?.delegate = self
UIScreenshotServiceDelegate
代理只有一個(gè)方法,讓我們來(lái)實(shí)現(xiàn)它
func screenshotService(_ screenshotService: UIScreenshotService, generatePDFRepresentationWithCompletion completionHandler: @escaping (Data?, Int, CGRect) -> Void) {
completionHandler(getScreenshotData(tableView), 0, CGRect.zero)
}
我們看一下這個(gè)回調(diào)巢音,第一個(gè)參數(shù)是 PDF 的 data 數(shù)據(jù)遵倦,第二個(gè)參數(shù)是 PDF 頁(yè)面的索引,第三個(gè)參數(shù)是 PDF 中相對(duì)于當(dāng)前頁(yè)面的坐標(biāo)官撼。getScreenshotData
是我自己寫(xiě)的方法梧躺,方法里的邏輯是 scrollView → image → PDF → data
,由于代碼不多傲绣,而且都能在網(wǎng)上找到掠哥,就不貼出來(lái)了。
說(shuō)一下思路秃诵,scrollView
轉(zhuǎn)成 image
的原理是 scrollView.frame = CGRect(origin: .zero, size: scrollView.contentSize)
注意:如果是 tableView
的話(huà)會(huì)導(dǎo)致所有 cell
都被加載出來(lái)续搀,如果當(dāng)前控制器是一個(gè)無(wú)限列表,請(qǐng)不要使用這個(gè)功能菠净。
Gestures
雙指滑動(dòng)手勢(shì)
iOS 13 中 tableView
和 collectionView
都增加雙指滑動(dòng)編輯的功能禁舷,在短信和備忘錄中都使用這個(gè)功能彪杉,接下來(lái)我們來(lái)看下效果。
這個(gè)功能體驗(yàn)上也是很爽的榛了,如果你的 App 中有相應(yīng)的場(chǎng)景在讶,建議加上這個(gè)功能煞抬,下面讓我們一起來(lái)看看怎么實(shí)現(xiàn)這個(gè)效果霜大。
首先設(shè)置 tableView.allowsMultipleSelectionDuringEditing = true
允許多選,然后實(shí)現(xiàn)兩個(gè)代理方法革答。
/// 是否允許多指選中
optional func tableView(_ tableView: UITableView, shouldBeginMultipleSelectionInteractionAtIndexPath indexPath: IndexPath) -> Bool
///多指選中開(kāi)始战坤,這里可以做一些UI修改,比如修改導(dǎo)航欄上按鈕的文本
optional func tableView(_ tableView: UITableView, didBeginMultipleSelectionInteractionAtIndexPath indexPath: IndexPath)
最后當(dāng)用戶(hù)選擇完残拐,要做某些操作的時(shí)候途茫,我們可以用 tableView.indexPathsForSelectedRows
獲取用戶(hù)選擇的 rows。
編輯手勢(shì)
- 復(fù)制:三指捏合
- 剪切:兩次三指捏合
- 粘貼:三指松開(kāi)
- 撤銷(xiāo):三指向左劃動(dòng)(或三指雙擊)
- 重做:三指向右劃動(dòng)
- 快捷菜單:三指單擊
iOS 13 增加了一些文本編輯的手勢(shì)溪食,這些手勢(shì)系統(tǒng)默認(rèn)會(huì)提供囊卜,如果我們想要禁用這些手勢(shì),需要重寫(xiě) editingInteractionConfiguration
屬性错沃,代碼如下栅组。
override var editingInteractionConfiguration: UIEditingInteractionConfiguration {
return .none
}
Presentations
iOS 13 下 present 的效果改成了這個(gè)樣子。
這樣帶來(lái)了新的交互方式枢析,下拉就可以 dismiss
控制器玉掸,實(shí)測(cè)這是個(gè)很爽的功能,體驗(yàn)大幅度提升醒叁,但是對(duì)我們開(kāi)發(fā)者來(lái)說(shuō)呢司浪,帶來(lái)了一些坑,下面讓我們來(lái)看看吧把沼。
首先 UIModalPresentationStyle
增加了一個(gè) automatic
屬性啊易,在 iOS 13 下默認(rèn)就是這個(gè)屬性。系統(tǒng)會(huì)根據(jù)推出的控制器來(lái)選擇是 pageSheet
還是 fullScreen
饮睬,比如當(dāng)我們用 UIImagePickerController
推出相機(jī)是 fullScreen
租谈,我們自己寫(xiě)的控制器是 pageSheet
。如果我們只想推出 fullScreen
的控制器也很簡(jiǎn)單续捂,present 之前設(shè)置 vc.modalPresentationStyle = .fullScreen
就好了垦垂。
接下來(lái)說(shuō)一下 pageSheet
的坑是什么,我們先來(lái)看下 fullScreen
的調(diào)用順序牙瓢。
再來(lái)看下
pageSheet
的調(diào)用順序劫拗。
當(dāng)A控制器 present B控制器,A控制器的 viewWillDisappear
和 viewDidDisappear
不會(huì)調(diào)用矾克,當(dāng)B控制器 dismiss页慷,A控制器的 viewWillAppear
和 viewDidAppear
也不會(huì)調(diào)用。也就是說(shuō)如果你有一些邏輯是放在這4個(gè)方法中的,要么把業(yè)務(wù)邏輯換個(gè)地方酒繁,要么設(shè)置 vc.modalPresentationStyle = .fullScreen
滓彰。
另外,UIViewController
增加一個(gè)了屬性 isModalInPresentation
州袒,默認(rèn)為 false揭绑,當(dāng)該屬性為 false 時(shí),用戶(hù)下拉可以 dismiss 控制器郎哭,為 true 時(shí)他匪,下拉不可以 dismiss控制器。該屬性可以配合有編輯功能的控制器使用夸研,讓我們來(lái)看下官方的 Demo
我們可以看到邦蜜,未編輯內(nèi)容時(shí)下拉可以 dismiss,編輯了內(nèi)容后下拉不可以dismiss亥至,同時(shí)彈出了一個(gè) alert 提示用戶(hù)要不要保存編輯過(guò)的內(nèi)容悼沈。詳細(xì)的代碼大家可以去 Demo 里看,這里就簡(jiǎn)單說(shuō)一下姐扮。
首先判斷用戶(hù)是否輸入絮供,有輸入將 isModalInPresentation
改為 true。然后實(shí)現(xiàn) UIAdaptivePresentationControllerDelegate
代理的 presentationControllerDidAttemptToDismiss:
方法溶握。這個(gè)方法會(huì)在 isModalInPresentation = true
杯缺,且用戶(hù)嘗試下拉 dismiss 控制器時(shí)調(diào)用。最后在這個(gè)方法里彈出 alert 提示用戶(hù)是否保存編輯過(guò)的內(nèi)容即可睡榆。
Search
iOS 13 下 UISearchViewController
結(jié)構(gòu)如下萍肆。
我們先來(lái)說(shuō)下
UISearchBar
的變化,現(xiàn)在我們可以在 UISearchBar
中獲取到 UISearchTextField
了胀屿,可以修改 field 的顏色塘揣、字體等,代碼如下宿崭。
let field = searchController.searchBar.searchTextField
field.textColor = UIColor.label
field.font = UIFont.systemFont(ofSize: 20)
其次增加了 Token
功能亲铡,Token
可以被復(fù)制、粘貼和拖拽葡兑,Token
還具有以下特點(diǎn):
1.始終在普通文本前面奖蔓;
2.可以被選中和刪除;
3.可以和普通文本一起被選中讹堤;
接下來(lái)我們看下如何創(chuàng)建
Token
吆鹤,我們有兩種創(chuàng)建 Token
的方式,代碼如下洲守。
// 第一種方式疑务,直接創(chuàng)建一個(gè) Token
let field = searchController.searchBar.searchTextField
field.insertToken(UISearchToken(icon: nil, text: "Token"), at: 0)
// 第二種方式沾凄,選擇一段文本,將其變成 Token知允,過(guò)程如圖
let field = searchController.searchBar.searchTextField
guard let selectedTextRange = field.selectedTextRange, !selectedTextRange.isEmpty else { return }
guard let selectedText = field.text(in: selectedTextRange) else { return } // "beach"
let token = UISearchToken(icon: nil, text: selectedText)
field.replaceTextualPortion(of: selectedTextRange, with: token, at: field.tokens.count)
此外撒蟀,系統(tǒng)還提供
textualRange
屬性,來(lái)獲取普通文本的長(zhǎng)度温鸽。
最后介紹一下 showsSearchResultsController
屬性保屯,該屬性可以控制是否展示搜索結(jié)果控制器。
Menus
還記得我在文章開(kāi)頭說(shuō)有些 API 一個(gè) beta 改一次嘛...沒(méi)錯(cuò)就是它 UIMenu
每個(gè) beta 寫(xiě)法都不一樣(吃棗藥丸)我們先來(lái)看下效果嗤朴。
我們分析一下動(dòng)圖里的結(jié)構(gòu)配椭,如圖
我們可以看到 UIMenu
可以嵌套 UIAction
也可以再嵌套 UIMenu
虫溜,下面讓我們一起來(lái)看看這是怎么實(shí)現(xiàn)的雹姊。
首先創(chuàng)建一個(gè) UIContextMenuInteraction
對(duì)象,將它加到對(duì)應(yīng)的 view
上衡楞。
let menuInteraction = UIContextMenuInteraction(delegate: self)
menuView.addInteraction(menuInteraction)
其次實(shí)現(xiàn) UIContextMenuInteractionDelegate
代理吱雏,配置 UIMenu
。
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
// 需要展示的控制器
return ViewController2()
}) { (list) -> UIMenu? in
let editMenu = UIMenu(title: "Edit...", image: nil, identifier: nil, options: [], children: [
UIAction(title: "Copy", image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
print("Copy")
}),
UIAction(title: "Duplicate", image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
print("Duplicate")
})
])
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: [
UIAction(title: "Share", image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { (_) in
print("Share")
}),
editMenu,
UIAction(title: "Delete", image: nil, identifier: nil, discoverabilityTitle: nil, attributes: [.destructive], state: .off, handler: { (_) in
print("Delete")
})
])
}
}
代碼有點(diǎn)多瘾境,但是不難歧杏,我們一點(diǎn)點(diǎn)來(lái)分析,另外圖片相關(guān)的代碼我刪掉了迷守,沒(méi)太多意義還會(huì)影響閱讀體驗(yàn)犬绒。
首先這個(gè)方法要求我們返回一個(gè) UIContextMenuConfiguration
對(duì)象,這個(gè)對(duì)象的初始化方法有3個(gè)參數(shù)兑凿,第一個(gè)是 identifier
凯力,第二個(gè)是一個(gè)閉包,要求返回要展示的控制器礼华,第三個(gè)也是個(gè)閉包咐鹤,要求返回 UIMenu
對(duì)象。
UIMenu
接下來(lái)我們看下 UIMenu
創(chuàng)建的過(guò)程圣絮, 首先創(chuàng)建了 editMenu 也就是動(dòng)圖中第二欄祈惶,點(diǎn)擊之后會(huì)再?gòu)棾鰞蓚€(gè) UIAction
,然后讓我們看看怎么創(chuàng)建 UIMenu
扮匠。
init(title: String,
image: UIImage? = nil,
identifier: UIMenu.Identifier? = nil,
options: UIMenu.Options = [],
children: [UIMenuElement] = [])
這里我們主要說(shuō)下 options
參數(shù)捧请,UIMenu.Options
聲明如下。
public struct Options : OptionSet {
public init(rawValue: UInt)
/// Show children inline in parent, instead of hierarchically
public static var displayInline: UIMenu.Options { get }
/// Indicates whether the menu should be rendered with a destructive appearance in its parent
public static var destructive: UIMenu.Options { get }
}
options
參數(shù)是用于第二層 menu 的棒搜,我們可以看到動(dòng)圖中的 Delete 是紅色的疹蛉,那是因?yàn)樗?UIAction
而且有對(duì)應(yīng)的屬性可以設(shè)置,那么如果我想把 Edit... 弄成成紅色就要設(shè)置 options = destructive
帮非。再說(shuō)下 displayInline
氧吐,這個(gè)效果是把第二層 menu 放到第一層來(lái)展示讹蘑,效果如下。
細(xì)心的小伙伴可能發(fā)現(xiàn)筑舅,options
是一個(gè) OptionSet
意味著可以同時(shí)設(shè)置兩個(gè)屬性座慰,那么設(shè)置兩個(gè)屬性會(huì)有什么效果呢,答案是:只有 displayInline
的效果翠拣,做成 OptionSet
應(yīng)該是為將來(lái)拓展用的版仔,目前是沒(méi)什么用的。
UIAction
接下來(lái)我們來(lái)看看 UIAction
的初始化方法误墓。
init(title: String,
image: UIImage? = nil,
identifier: UIAction.Identifier? = nil,
discoverabilityTitle: String? = nil,
attributes: UIMenuElement.Attributes = [],
state: UIMenuElement.State = .off,
handler: @escaping UIActionHandler)
前三個(gè)參數(shù)就不說(shuō)了蛮粮,第四個(gè)參數(shù) discoverabilityTitle
這個(gè)參數(shù)我目前沒(méi)有研究出來(lái)是干嘛用的,如果有知道的小伙伴歡迎在評(píng)論區(qū)留言谜慌。
第五個(gè)參數(shù) attributes
然想,我們先來(lái)看下聲明和效果圖。
public struct Attributes : OptionSet {
public init(rawValue: UInt)
public static var disabled: UIMenuElement.Attributes { get }
public static var destructive: UIMenuElement.Attributes { get }
public static var hidden: UIMenuElement.Attributes { get }
}
attributes
也是 OptionSet
可以多個(gè)一起用欣范,但是這幾個(gè)組合都沒(méi)用变泄。
第六個(gè)參數(shù) state
,一樣先看聲明和效果圖恼琼。
public enum State : Int {
case off
case on
case mixed
}
state
可以和 attributes
搭配使用妨蛹,on
和 mixed
的區(qū)別我目前沒(méi)找到,另外如果 UIAction
設(shè)置了圖片同時(shí)設(shè)置了 state = .on
則會(huì)把圖片覆蓋掉晴竞,只留下一個(gè)勾勾蛙卤。第七個(gè)參數(shù)是個(gè)閉包,當(dāng)用戶(hù)點(diǎn)擊后會(huì)進(jìn)入回調(diào)噩死,處理相應(yīng)的邏輯即可颤难。
最后我們把
UIAction
和 editMenu 一起放到一個(gè)新的 UIMenu
中就可以達(dá)到動(dòng)圖中的效果了。
以上是 iOS 13 部分新特性的介紹甜滨,如有錯(cuò)誤歡迎指出乐严。
WWDC鏈接 Modernizing Your UI for iOS 13
如果你想知道 iOS 13 怎么適配夜間模式可以閱讀這篇文章。