未经受权,禁止转载swift
原文:juejin.im/post/5d3e8f…api
转眼间 WWDC 19 已通过去1个多月了,这篇文章本应该很早就写的,可是有些代码 beta1-beta4 一个 beta 变一次 API,并且以前几个 beta 部分初始化方法仍是以 __ 开头的私有方法(无力吐槽),因此拖到如今 beta4 API 基本稳定了才开始写这篇文章。浏览器
若是你已经升到 iOS 13 你会发现当你在 Safari 中截图后有一个 “整页” 的功能,能够把当前的 HTML 转成 PDF 存到 “文件” 中。那么你可能会想了,这个新特性雨窝无瓜啊,我又不作浏览器的 App。其实咱们能够把 scrollView
转成 image
,再把 image
转成 PDF,这样咱们就能够把这个 scrollView
作成一个长图了,咱们先来看下效果。闭包
怎么样是否是挺不错的,接下来就让咱们来看看这是怎么实现的。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
iOS 13 中 tableView
和 collectionView
都增长双指滑动编辑的功能,在短信和备忘录中都使用这个功能,接下来咱们来看下效果。
这个功能体验上也是很爽的,若是你的 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
}
复制代码
iOS 13 下 present 的效果改为了这个样子。
这样带来了新的交互方式,下拉就能够 dismiss
控制器,实测这是个很爽的功能,体验大幅度提高,可是对咱们开发者来讲呢,带来了一些坑,下面让咱们来看看吧。
首先 UIModalPresentationStyle
增长了一个 automatic
属性,在 iOS 13 下默认就是这个属性。系统会根据推出的控制器来选择是 pageSheet
仍是 fullScreen
,好比当咱们用 UIImagePickerController
推出相机是 fullScreen
,咱们本身写的控制器是 pageSheet
。若是咱们只想推出 fullScreen
的控制器也很简单,present 以前设置 vc.modalPresentationStyle = .fullScreen
就行了。
接下来讲一下 pageSheet
的坑是什么,咱们先来看下 fullScreen
的调用顺序。
pageSheet
的调用顺序。
当A控制器 present B控制器,A控制器的 viewWillDisappear
和 viewDidDisappear
不会调用,当B控制器 dismiss,A控制器的 viewWillAppear
和 viewDidAppear
也不会调用。也就是说若是你有一些逻辑是放在这4个方法中的,要么把业务逻辑换个地方,要么设置 vc.modalPresentationStyle = .fullScreen
。
另外,UIViewController
增长一个了属性 isModalInPresentation
,默认为 false,当该属性为 false 时,用户下拉能够 dismiss 控制器,为 true 时,下拉不能够 dismiss控制器。该属性能够配合有编辑功能的控制器使用,让咱们来看下官方的 Demo
咱们能够看到,未编辑内容时下拉能够 dismiss,编辑了内容后下拉不能够dismiss,同时弹出了一个 alert 提示用户要不要保存编辑过的内容。详细的代码你们能够去 Demo 里看,这里就简单说一下。
首先判断用户是否输入,有输入将 isModalInPresentation
改成 true。而后实现 UIAdaptivePresentationControllerDelegate
代理的 presentationControllerDidAttemptToDismiss:
方法。这个方法会在 isModalInPresentation = true
,且用户尝试下拉 dismiss 控制器时调用。最后在这个方法里弹出 alert 提示用户是否保存编辑过的内容便可。
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
还具备如下特色:
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
属性,该属性能够控制是否展现搜索结果控制器。
还记得我在文章开头说有些 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
建立的过程, 首先建立了 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
的初始化方法。
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
搭配使用,
on
和
mixed
的区别我目前没找到,另外若是
UIAction
设置了图片同时设置了
state = .on
则会把图片覆盖掉,只留下一个勾勾。
第七个参数是个闭包,当用户点击后会进入回调,处理相应的逻辑便可。
最后咱们把 UIAction
和 editMenu 一块儿放到一个新的 UIMenu
中就能够达到动图中的效果了。
以上是 iOS 13 部分新特性的介绍,若有错误欢迎指出。
WWDC连接 Modernizing Your UI for iOS 13
若是你想知道 iOS 13 怎么适配夜间模式能够阅读这篇文章。