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

未经授权,禁止转载
原文:https://www.jianshu.com/p/01cda53e2fc8

转眼间 WWDC 19 已经过去1个多月了,这篇文章本应该很早就写的,但是有些代码 beta1-beta4 一个 beta 变一次 API,而且之前几个 beta 部分初始化方法还是以 __ 开头的私有方法(无力吐槽),所以拖到现在 beta4 API 基本稳定了才开始写这篇文章。

目录

  • PDF
  • Gestures
  • Presentations
  • Search
  • Menus

PDF(长图)

如果你已经升到 iOS 13 你会发现当你在 Safari 中截图后有一个 “整页” 的功能,可以把当前的 HTML 转成 PDF 存到 “文件” 中。那么你可能会想了,这个新特性雨窝无瓜啊,我又不做浏览器的 App。其实我们可以把 scrollView 转成 image,再把 image 转成 PDF,这样我们就可以把这个 scrollView 做成一个长图了,我们先来看下效果。

iOS13-新特性(PDF/Search/Menus)_第1张图片
注.我将pdf转成了jpeg

怎么样是不是挺不错的,接下来就让我们来看看这是怎么实现的。
首先我们要在控制器中实现 UIScreenshotServiceDelegate 代理,由于 iOS 13 项目结构发生了变化,这里列出两种设置代理的方式。

// iOS 13项目结构
let scene = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
scene?.window?.windowScene?.screenshotService?.delegate = self

// iOS13之前项目结构
UIApplication.shared.keyWindow?.windowScene?.screenshotService?.delegate = self

UIScreenshotServiceDelegate 代理只有一个方法,让我们来实现它

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)
注意:如果是 tableView 的话会导致所有 cell 都被加载出来,如果当前控制器是一个无限列表,请不要使用这个功能。

Gestures

双指滑动手势

iOS 13 中 tableViewcollectionView 都增加双指滑动编辑的功能,在短信和备忘录中都使用这个功能,接下来我们来看下效果。

iOS13-新特性(PDF/Search/Menus)_第2张图片

这个功能体验上也是很爽的,如果你的 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 的效果改成了这个样子。


iOS13-新特性(PDF/Search/Menus)_第3张图片

这样带来了新的交互方式,下拉就可以 dismiss 控制器,实测这是个很爽的功能,体验大幅度提升,但是对我们开发者来说呢,带来了一些坑,下面让我们来看看吧。

首先 UIModalPresentationStyle 增加了一个 automatic 属性,在 iOS 13 下默认就是这个属性。系统会根据推出的控制器来选择是 pageSheet 还是 fullScreen,比如当我们用 UIImagePickerController 推出相机是 fullScreen,我们自己写的控制器是 pageSheet。如果我们只想推出 fullScreen 的控制器也很简单,present 之前设置 vc.modalPresentationStyle = .fullScreen 就好了。

接下来说一下 pageSheet 的坑是什么,我们先来看下 fullScreen 的调用顺序。

iOS13-新特性(PDF/Search/Menus)_第4张图片
fullScreen

再来看下 pageSheet 的调用顺序。

iOS13-新特性(PDF/Search/Menus)_第5张图片
pageSheet

当A控制器 present B控制器,A控制器的 viewWillDisappearviewDidDisappear 不会调用,当B控制器 dismiss,A控制器的 viewWillAppearviewDidAppear 也不会调用。也就是说如果你有一些逻辑是放在这4个方法中的,要么把业务逻辑换个地方,要么设置 vc.modalPresentationStyle = .fullScreen

另外,UIViewController 增加一个了属性 isModalInPresentation,默认为 false,当该属性为 false 时,用户下拉可以 dismiss 控制器,为 true 时,下拉不可以 dismiss控制器。该属性可以配合有编辑功能的控制器使用,让我们来看下官方的 Demo

iOS13-新特性(PDF/Search/Menus)_第6张图片

我们可以看到,未编辑内容时下拉可以 dismiss,编辑了内容后下拉不可以dismiss,同时弹出了一个 alert 提示用户要不要保存编辑过的内容。详细的代码大家可以去 Demo 里看,这里就简单说一下。
首先判断用户是否输入,有输入将 isModalInPresentation 改为 true。然后实现 UIAdaptivePresentationControllerDelegate 代理的 presentationControllerDidAttemptToDismiss: 方法。这个方法会在 isModalInPresentation = true,且用户尝试下拉 dismiss 控制器时调用。最后在这个方法里弹出 alert 提示用户是否保存编辑过的内容即可。

Search

iOS 13 下 UISearchViewController 结构如下。

iOS13-新特性(PDF/Search/Menus)_第7张图片

我们先来说下 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.可以和普通文本一起被选中;

iOS13-新特性(PDF/Search/Menus)_第8张图片

接下来我们看下如何创建 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)

iOS13-新特性(PDF/Search/Menus)_第9张图片

此外,系统还提供 textualRange 属性,来获取普通文本的长度。

iOS13-新特性(PDF/Search/Menus)_第10张图片

最后介绍一下 showsSearchResultsController 属性,该属性可以控制是否展示搜索结果控制器。

Menus

还记得我在文章开头说有些 API 一个 beta 改一次嘛...没错就是它 UIMenu 每个 beta 写法都不一样(吃枣药丸)我们先来看下效果。

iOS13-新特性(PDF/Search/Menus)_第11张图片

我们分析一下动图里的结构,如图


iOS13-新特性(PDF/Search/Menus)_第12张图片

我们可以看到 UIMenu 可以嵌套 UIAction 也可以再嵌套 UIMenu,下面让我们一起来看看这是怎么实现的。
首先创建一个 UIContextMenuInteraction 对象,将它加到对应的 view 上。

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 放到第一层来展示,效果如下。

iOS13-新特性(PDF/Search/Menus)_第13张图片

细心的小伙伴可能发现,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 }
}
iOS13-新特性(PDF/Search/Menus)_第14张图片

attributes 也是 OptionSet 可以多个一起用,但是这几个组合都没用。

第六个参数 state,一样先看声明和效果图。

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

iOS13-新特性(PDF/Search/Menus)_第15张图片

state 可以和 attributes 搭配使用, onmixed 的区别我目前没找到,另外如果 UIAction 设置了图片同时设置了 state = .on 则会把图片覆盖掉,只留下一个勾勾。
第七个参数是个闭包,当用户点击后会进入回调,处理相应的逻辑即可。
最后我们把 UIAction 和 editMenu 一起放到一个新的 UIMenu 中就可以达到动图中的效果了。


以上是 iOS 13 部分新特性的介绍,如有错误欢迎指出。
WWDC链接 Modernizing Your UI for iOS 13

如果你想知道 iOS 13 怎么适配夜间模式可以阅读这篇文章。

你可能感兴趣的:(iOS13-新特性(PDF/Search/Menus))