iOS13-新特性(PDF/Search/Menus)

未經(jīng)授權(quán),禁止轉(zhuǎn)載
原文:http://www.lxweimin.com/p/01cda53e2fc8

轉(zhuǎn)眼間 WWDC 19 已經(jīng)過去1個多月了,這篇文章本應(yīng)該很早就寫的,但是有些代碼 beta1-beta4 一個 beta 變一次 API,而且之前幾個 beta 部分初始化方法還是以 __ 開頭的私有方法(無力吐槽),所以拖到現(xiàn)在 beta4 API 基本穩(wěn)定了才開始寫這篇文章。

目錄

  • PDF
  • Gestures
  • Presentations
  • Search
  • Menus

PDF(長圖)

如果你已經(jīng)升到 iOS 13 你會發(fā)現(xiàn)當(dāng)你在 Safari 中截圖后有一個 “整頁” 的功能,可以把當(dāng)前的 HTML 轉(zhuǎn)成 PDF 存到 “文件” 中。那么你可能會想了,這個新特性雨窩無瓜啊,我又不做瀏覽器的 App。其實我們可以把 scrollView 轉(zhuǎn)成 image,再把 image 轉(zhuǎn)成 PDF,這樣我們就可以把這個 scrollView 做成一個長圖了,我們先來看下效果。

注.我將pdf轉(zhuǎn)成了jpeg

怎么樣是不是挺不錯的,接下來就讓我們來看看這是怎么實現(xiàn)的。
首先我們要在控制器中實現(xiàn) UIScreenshotServiceDelegate 代理,由于 iOS 13 項目結(jié)構(gòu)發(fā)生了變化,這里列出兩種設(shè)置代理的方式。

// iOS 13項目結(jié)構(gòu)
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.windowScene?.screenshotService?.delegate = self

// iOS13之前項目結(jié)構(gòu)
UIApplication.shared.keyWindow?.windowScene?.screenshotService?.delegate = self

UIScreenshotServiceDelegate 代理只有一個方法,讓我們來實現(xiàn)它

func screenshotService(_ screenshotService: UIScreenshotService, generatePDFRepresentationWithCompletion completionHandler: @escaping (Data?, Int, CGRect) -> Void) {
    completionHandler(getScreenshotData(tableView), 0, CGRect.zero)
}

我們看一下這個回調(diào),第一個參數(shù)是 PDF 的 data 數(shù)據(jù),第二個參數(shù)是 PDF 頁面的索引,第三個參數(shù)是 PDF 中相對于當(dāng)前頁面的坐標(biāo)。getScreenshotData 是我自己寫的方法,方法里的邏輯是 scrollView → image → PDF → data,由于代碼不多,而且都能在網(wǎng)上找到,就不貼出來了。
說一下思路,scrollView 轉(zhuǎn)成 image 的原理是 scrollView.frame = CGRect(origin: .zero, size: scrollView.contentSize)
注意:如果是 tableView 的話會導(dǎo)致所有 cell 都被加載出來,如果當(dāng)前控制器是一個無限列表,請不要使用這個功能。

Gestures

雙指滑動手勢

iOS 13 中 tableViewcollectionView 都增加雙指滑動編輯的功能,在短信和備忘錄中都使用這個功能,接下來我們來看下效果。

這個功能體驗上也是很爽的,如果你的 App 中有相應(yīng)的場景,建議加上這個功能,下面讓我們一起來看看怎么實現(xiàn)這個效果。
首先設(shè)置 tableView.allowsMultipleSelectionDuringEditing = true 允許多選,然后實現(xiàn)兩個代理方法。

/// 是否允許多指選中
optional func tableView(_ tableView: UITableView, shouldBeginMultipleSelectionInteractionAtIndexPath indexPath: IndexPath) -> Bool

///多指選中開始,這里可以做一些UI修改,比如修改導(dǎo)航欄上按鈕的文本
optional func tableView(_ tableView: UITableView, didBeginMultipleSelectionInteractionAtIndexPath indexPath: IndexPath) 

最后當(dāng)用戶選擇完,要做某些操作的時候,我們可以用 tableView.indexPathsForSelectedRows 獲取用戶選擇的 rows。

編輯手勢

  • 復(fù)制:三指捏合
  • 剪切:兩次三指捏合
  • 粘貼:三指松開
  • 撤銷:三指向左劃動(或三指雙擊)
  • 重做:三指向右劃動
  • 快捷菜單:三指單擊

iOS 13 增加了一些文本編輯的手勢,這些手勢系統(tǒng)默認會提供,如果我們想要禁用這些手勢,需要重寫 editingInteractionConfiguration 屬性,代碼如下。

override var editingInteractionConfiguration: UIEditingInteractionConfiguration {
    return .none
}

Presentations

iOS 13 下 present 的效果改成了這個樣子。


這樣帶來了新的交互方式,下拉就可以 dismiss 控制器,實測這是個很爽的功能,體驗大幅度提升,但是對我們開發(fā)者來說呢,帶來了一些坑,下面讓我們來看看吧。

首先 UIModalPresentationStyle 增加了一個 automatic 屬性,在 iOS 13 下默認就是這個屬性。系統(tǒng)會根據(jù)推出的控制器來選擇是 pageSheet 還是 fullScreen,比如當(dāng)我們用 UIImagePickerController 推出相機是 fullScreen,我們自己寫的控制器是 pageSheet。如果我們只想推出 fullScreen 的控制器也很簡單,present 之前設(shè)置 vc.modalPresentationStyle = .fullScreen 就好了。

接下來說一下 pageSheet 的坑是什么,我們先來看下 fullScreen 的調(diào)用順序。

fullScreen

再來看下 pageSheet 的調(diào)用順序。

pageSheet

當(dāng)A控制器 present B控制器,A控制器的 viewWillDisappearviewDidDisappear 不會調(diào)用,當(dāng)B控制器 dismiss,A控制器的 viewWillAppearviewDidAppear 也不會調(diào)用。也就是說如果你有一些邏輯是放在這4個方法中的,要么把業(yè)務(wù)邏輯換個地方,要么設(shè)置 vc.modalPresentationStyle = .fullScreen

另外,UIViewController 增加一個了屬性 isModalInPresentation,默認為 false,當(dāng)該屬性為 false 時,用戶下拉可以 dismiss 控制器,為 true 時,下拉不可以 dismiss控制器。該屬性可以配合有編輯功能的控制器使用,讓我們來看下官方的 Demo

我們可以看到,未編輯內(nèi)容時下拉可以 dismiss,編輯了內(nèi)容后下拉不可以dismiss,同時彈出了一個 alert 提示用戶要不要保存編輯過的內(nèi)容。詳細的代碼大家可以去 Demo 里看,這里就簡單說一下。
首先判斷用戶是否輸入,有輸入將 isModalInPresentation 改為 true。然后實現(xiàn) UIAdaptivePresentationControllerDelegate 代理的 presentationControllerDidAttemptToDismiss: 方法。這個方法會在 isModalInPresentation = true,且用戶嘗試下拉 dismiss 控制器時調(diào)用。最后在這個方法里彈出 alert 提示用戶是否保存編輯過的內(nèi)容即可。

Search

iOS 13 下 UISearchViewController 結(jié)構(gòu)如下。


我們先來說下 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 還具有以下特點:
1.始終在普通文本前面;
2.可以被選中和刪除;
3.可以和普通文本一起被選中;


接下來我們看下如何創(chuàng)建 Token,我們有兩種創(chuàng)建 Token 的方式,代碼如下。

// 第一種方式,直接創(chuàng)建一個 Token
let field = searchController.searchBar.searchTextField
field.insertToken(UISearchToken(icon: nil, text: "Token"), at: 0)

// 第二種方式,選擇一段文本,將其變成 Token,過程如圖
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 屬性,來獲取普通文本的長度。

最后介紹一下 showsSearchResultsController 屬性,該屬性可以控制是否展示搜索結(jié)果控制器。

Menus

還記得我在文章開頭說有些 API 一個 beta 改一次嘛...沒錯就是它 UIMenu 每個 beta 寫法都不一樣(吃棗藥丸)我們先來看下效果。

我們分析一下動圖里的結(jié)構(gòu),如圖


我們可以看到 UIMenu 可以嵌套 UIAction 也可以再嵌套 UIMenu,下面讓我們一起來看看這是怎么實現(xiàn)的。
首先創(chuàng)建一個 UIContextMenuInteraction 對象,將它加到對應(yīng)的 view 上。

let menuInteraction = UIContextMenuInteraction(delegate: self)
menuView.addInteraction(menuInteraction)

其次實現(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")
            })
        ])
    }
}

代碼有點多,但是不難,我們一點點來分析,另外圖片相關(guān)的代碼我刪掉了,沒太多意義還會影響閱讀體驗。
首先這個方法要求我們返回一個 UIContextMenuConfiguration 對象,這個對象的初始化方法有3個參數(shù),第一個是 identifier,第二個是一個閉包,要求返回要展示的控制器,第三個也是個閉包,要求返回 UIMenu 對象。

UIMenu

接下來我們看下 UIMenu 創(chuàng)建的過程, 首先創(chuàng)建了 editMenu 也就是動圖中第二欄,點擊之后會再彈出兩個 UIAction,然后讓我們看看怎么創(chuàng)建 UIMenu

init(title: String, 
     image: UIImage? = nil, 
     identifier: UIMenu.Identifier? = nil, 
     options: UIMenu.Options = [], 
     children: [UIMenuElement] = [])

這里我們主要說下 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 的,我們可以看到動圖中的 Delete 是紅色的,那是因為它是 UIAction 而且有對應(yīng)的屬性可以設(shè)置,那么如果我想把 Edit... 弄成成紅色就要設(shè)置 options = destructive。再說下 displayInline,這個效果是把第二層 menu 放到第一層來展示,效果如下。

細心的小伙伴可能發(fā)現(xiàn),options 是一個 OptionSet 意味著可以同時設(shè)置兩個屬性,那么設(shè)置兩個屬性會有什么效果呢,答案是:只有 displayInline 的效果,做成 OptionSet 應(yīng)該是為將來拓展用的,目前是沒什么用的。

UIAction

接下來我們來看看 UIAction 的初始化方法。

init(title: String, 
     image: UIImage? = nil, 
     identifier: UIAction.Identifier? = nil, 
     discoverabilityTitle: String? = nil, 
     attributes: UIMenuElement.Attributes = [], 
     state: UIMenuElement.State = .off, 
     handler: @escaping UIActionHandler)

前三個參數(shù)就不說了,第四個參數(shù) discoverabilityTitle 這個參數(shù)我目前沒有研究出來是干嘛用的,如果有知道的小伙伴歡迎在評論區(qū)留言。
第五個參數(shù) attributes,我們先來看下聲明和效果圖。

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 可以多個一起用,但是這幾個組合都沒用。

第六個參數(shù) state,一樣先看聲明和效果圖。

public enum State : Int {
    case off
    case on
    case mixed
}


state 可以和 attributes 搭配使用,onmixed 的區(qū)別我目前沒找到,另外如果 UIAction 設(shè)置了圖片同時設(shè)置了 state = .on 則會把圖片覆蓋掉,只留下一個勾勾。
第七個參數(shù)是個閉包,當(dāng)用戶點擊后會進入回調(diào),處理相應(yīng)的邏輯即可。
最后我們把 UIAction 和 editMenu 一起放到一個新的 UIMenu 中就可以達到動圖中的效果了。


以上是 iOS 13 部分新特性的介紹,如有錯誤歡迎指出。
WWDC鏈接 Modernizing Your UI for iOS 13

如果你想知道 iOS 13 怎么適配夜間模式可以閱讀這篇文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容