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

未經受權,禁止轉載swift

原文:juejin.im/post/5d3e8f…api

轉眼間 WWDC 19 已通過去1個多月了,這篇文章本應該很早就寫的,可是有些代碼 beta1-beta4 一個 beta 變一次 API,並且以前幾個 beta 部分初始化方法仍是以 __ 開頭的私有方法(無力吐槽),因此拖到如今 beta4 API 基本穩定了纔開始寫這篇文章。瀏覽器

PDF(長圖)

若是你已經升到 iOS 13 你會發現當你在 Safari 中截圖後有一個 「整頁」 的功能,能夠把當前的 HTML 轉成 PDF 存到 「文件」 中。那麼你可能會想了,這個新特性雨窩無瓜啊,我又不作瀏覽器的 App。其實咱們能夠把 scrollView 轉成 image,再把 image 轉成 PDF,這樣咱們就能夠把這個 scrollView 作成一個長圖了,咱們先來看下效果。閉包

注.我將pdf轉成了jpeg

怎麼樣是否是挺不錯的,接下來就讓咱們來看看這是怎麼實現的。app

首先咱們要在控制器中實現 UIScreenshotServiceDelegate 代理,因爲 iOS 13 項目結構發生了變化,這裏列出兩種設置代理的方式。ide

// iOS 13項目結構
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.windowScene?.screenshotService?.delegate = self

// iOS 13以前項目結構
UIApplication.shared.keyWindow?.windowScene?.screenshotService?.delegate = self
複製代碼

UIScreenshotServiceDelegate 代理只有一個方法,讓咱們來實現它post

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

咱們看一下這個回調,第一個參數是 PDF 的 data 數據,第二個參數是 PDF 頁面的索引,第三個參數是 PDF 中相對於當前頁面的座標。getScreenshotData 是我本身寫的方法,方法裏的邏輯是 scrollView → image → PDF → data,因爲代碼很少,並且都能在網上找到,就不貼出來了。字體

說一下思路,scrollView 轉成 image 的原理是 scrollView.frame = CGRect(origin: .zero, size: scrollView.contentSize)ui

注意: 若是是 tableView 的話會致使全部 cell 都被加載出來,若是當前控制器是一個無限列表,請不要使用這個功能。spa

Gestures

雙指滑動手勢

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

這個功能體驗上也是很爽的,若是你的 App 中有相應的場景,建議加上這個功能,下面讓咱們一塊兒來看看怎麼實現這個效果。

首先設置 tableView.allowsMultipleSelectionDuringEditing = true 容許多選,而後實現兩個代理方法。

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

///多指選中開始,這裏能夠作一些UI修改,好比修改導航欄上按鈕的文本
optional func tableView(_ tableView: UITableView, didBeginMultipleSelectionInteractionAtIndexPath indexPath: IndexPath) 
複製代碼

最後當用戶選擇完,要作某些操做的時候,咱們能夠用 tableView.indexPathsForSelectedRows 獲取用戶選擇的 rows。

編輯手勢

  • 複製:三指捏合
  • 剪切:兩次三指捏合
  • 粘貼:三指鬆開
  • 撤銷:三指向左划動(或三指雙擊)
  • 重作:三指向右划動
  • 快捷菜單:三指單擊

iOS 13 增長了一些文本編輯的手勢,這些手勢系統默認會提供,若是咱們想要禁用這些手勢,須要重寫 editingInteractionConfiguration 屬性,代碼以下。

override var editingInteractionConfiguration: UIEditingInteractionConfiguration {
    return .none
}
複製代碼

Presentations

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

這樣帶來了新的交互方式,下拉就能夠 dismiss 控制器,實測這是個很爽的功能,體驗大幅度提高,可是對咱們開發者來講呢,帶來了一些坑,下面讓咱們來看看吧。

首先 UIModalPresentationStyle 增長了一個 automatic 屬性,在 iOS 13 下默認就是這個屬性。系統會根據推出的控制器來選擇是 pageSheet 仍是 fullScreen,好比當咱們用 UIImagePickerController 推出相機是 fullScreen,咱們本身寫的控制器是 pageSheet。若是咱們只想推出 fullScreen 的控制器也很簡單,present 以前設置 vc.modalPresentationStyle = .fullScreen 就行了。

接下來講一下 pageSheet 的坑是什麼,咱們先來看下 fullScreen 的調用順序。

fullScreen
再來看下 pageSheet 的調用順序。

pageSheet

當A控制器 present B控制器,A控制器的 viewWillDisappearviewDidDisappear 不會調用,當B控制器 dismiss,A控制器的 viewWillAppearviewDidAppear 也不會調用。也就是說若是你有一些邏輯是放在這4個方法中的,要麼把業務邏輯換個地方,要麼設置 vc.modalPresentationStyle = .fullScreen

另外,UIViewController 增長一個了屬性 isModalInPresentation,默認爲 false,當該屬性爲 false 時,用戶下拉能夠 dismiss 控制器,爲 true 時,下拉不能夠 dismiss控制器。該屬性能夠配合有編輯功能的控制器使用,讓咱們來看下官方的 Demo

咱們能夠看到,未編輯內容時下拉能夠 dismiss,編輯了內容後下拉不能夠dismiss,同時彈出了一個 alert 提示用戶要不要保存編輯過的內容。詳細的代碼你們能夠去 Demo 裏看,這裏就簡單說一下。

首先判斷用戶是否輸入,有輸入將 isModalInPresentation 改成 true。而後實現 UIAdaptivePresentationControllerDelegate 代理的 presentationControllerDidAttemptToDismiss: 方法。這個方法會在 isModalInPresentation = true,且用戶嘗試下拉 dismiss 控制器時調用。最後在這個方法裏彈出 alert 提示用戶是否保存編輯過的內容便可。

Search

iOS 13 下 UISearchViewController 結構以下。

咱們先來講下 UISearchBar 的變化,如今咱們能夠在 UISearchBar 中獲取到 UISearchTextField 了,能夠修改 field 的顏色、字體等,代碼以下。

let field = searchController.searchBar.searchTextField
field.textColor = UIColor.label
field.font = UIFont.systemFont(ofSize: 20)
複製代碼

其次增長了 Token 功能,Token 能夠被複制、粘貼和拖拽,Token 還具備如下特色:

  1. 始終在普通文本前面;
  2. 能夠被選中和刪除;
  3. 能夠和普通文本一塊兒被選中;

接下來咱們看下如何建立 Token,咱們有兩種建立 Token 的方式,代碼以下。

// 第一種方式,直接建立一個 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)
複製代碼

此外,系統還提供 textualRange 屬性,來獲取普通文本的長度。

最後介紹一下 showsSearchResultsController 屬性,該屬性能夠控制是否展現搜索結果控制器。

Menus

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

咱們分析一下動圖裏的結構,如圖

咱們能夠看到 UIMenu 能夠嵌套 UIAction 也能夠再嵌套 UIMenu,下面讓咱們一塊兒來看看這是怎麼實現的。 首先建立一個 UIContextMenuInteraction 對象,將它加到對應的 view 上。

let menuInteraction = UIContextMenuInteraction(delegate: self)
menuView.addInteraction(menuInteraction)
複製代碼

其次實現 UIContextMenuInteractionDelegate 代理,配置 UIMenu

let menuInteraction = UIContextMenuInteraction(delegate: self)
menuView.addInteraction(menuInteraction)
複製代碼

其次實現 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")
            })
        ])
    }
}
複製代碼

代碼有點多,可是不難,咱們一點點來分析,另外圖片相關的代碼我刪掉了,沒太多意義還會影響閱讀體驗。

首先這個方法要求咱們返回一個 UIContextMenuConfiguration 對象,這個對象的初始化方法有3個參數,第一個是 identifier,第二個是一個閉包,要求返回要展現的控制器,第三個也是個閉包,要求返回 UIMenu 對象。

UIMenu

接下來咱們看下 UIMenu 建立的過程, 首先建立了 editMenu 也就是動圖中第二欄,點擊以後會再彈出兩個 UIAction,而後讓咱們看看怎麼建立 UIMenu

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

這裏咱們主要說下 options 參數,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 參數是用於第二層 menu 的,咱們能夠看到動圖中的 Delete 是紅色的,那是由於它是 UIAction 並且有對應的屬性能夠設置,那麼若是我想把 Edit... 弄成成紅色就要設置 options = destructive。再說下 displayInline,這個效果是把第二層 menu 放到第一層來展現,效果以下。

細心的小夥伴可能發現,options 是一個 OptionSet 意味着能夠同時設置兩個屬性,那麼設置兩個屬性會有什麼效果呢,答案是:只有 displayInline 的效果,作成 OptionSet 應該是爲未來拓展用的,目前是沒什麼用的。

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)
複製代碼

前三個參數就不說了,第四個參數 discoverabilityTitle 這個參數我目前沒有研究出來是幹嗎用的,若是有知道的小夥伴歡迎在評論區留言。

第五個參數 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 能夠多個一塊兒用,可是這幾個組合都沒用。

第六個參數 state,同樣先看聲明和效果圖。

public enum State : Int {
    case off
    case on
    case mixed
}
複製代碼

state 能夠和 attributes 搭配使用, onmixed 的區別我目前沒找到,另外若是 UIAction 設置了圖片同時設置了 state = .on 則會把圖片覆蓋掉,只留下一個勾勾。

第七個參數是個閉包,當用戶點擊後會進入回調,處理相應的邏輯便可。

最後咱們把 UIAction 和 editMenu 一塊兒放到一個新的 UIMenu 中就能夠達到動圖中的效果了。


以上是 iOS 13 部分新特性的介紹,若有錯誤歡迎指出。

WWDC連接 Modernizing Your UI for iOS 13

若是你想知道 iOS 13 怎麼適配夜間模式能夠閱讀這篇文章

相關文章
相關標籤/搜索