如何复制iOS 10的Apple Music“Peek and pop action menu”

iOS 10有一个我想要复制的function。 当您在Apple Music应用程序中3D触摸相册时,它会打开下面显示的菜单。 然而,与普通的偷窥和流行音乐不同,当你举起手指时,它不会消失。 我该如何复制这个?

在此处输入图像描述

我最接近复制它的是以下代码..它创建了音乐应用程序的虚拟副本..然后我添加了PeekPop-3D-Touch代表。

但是,在委托中,我向手势识别器添加了一个观察者,然后在偷看时取消手势,然后在手指抬起时重新启用手势。 为了重新启用它,我做了异步,因为预览会在没有异步调度的情况下立即消失。 我无法找到解决方法..

现在,如果你点击蓝框外,它会像正常情况一样消失=]

http://i.imgur.com/073M2Ku.jpg http://i.imgur.com/XkwUBly.jpg

在此处输入图像描述 在此处输入图像描述

// // ViewController.swift // PeekPopExample // // Created by Brandon Anthony on 2016-07-16. // Copyright © 2016 XIO. All rights reserved. // import UIKit class MusicViewController: UITabBarController, UITabBarControllerDelegate { var tableView: UITableView! var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() self.initControllers() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } func initControllers() { let libraryController = LibraryViewController() let forYouController = UIViewController() let browseController = UIViewController() let radioController = UIViewController() let searchController = UIViewController() libraryController.title = "Library" libraryController.tabBarItem.image = nil forYouController.title = "For You" forYouController.tabBarItem.image = nil browseController.title = "Browse" browseController.tabBarItem.image = nil radioController.title = "Radio" radioController.tabBarItem.image = nil searchController.title = "Search" searchController.tabBarItem.image = nil self.viewControllers = [libraryController, forYouController, browseController, radioController, searchController]; } } 

而ForceTouch的执行暂停..

 // // LibraryViewController.swift // PeekPopExample // // Created by Brandon Anthony on 2016-07-16. // Copyright © 2016 XIO. All rights reserved. // import Foundation import UIKit //Views and Cells.. class AlbumView : UIView { var albumCover: UIImageView! var title: UILabel! var artist: UILabel! override init(frame: CGRect) { super.init(frame: frame) self.initControls() self.setTheme() self.doLayout() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func initControls() { self.albumCover = UIImageView() self.title = UILabel() self.artist = UILabel() } func setTheme() { self.albumCover.contentMode = .scaleAspectFit self.albumCover.layer.cornerRadius = 5.0 self.albumCover.backgroundColor = UIColor.lightGray() self.title.text = "Unknown" self.title.font = UIFont.systemFont(ofSize: 12) self.artist.text = "Unknown" self.artist.textColor = UIColor.lightGray() self.artist.font = UIFont.systemFont(ofSize: 12) } func doLayout() { self.addSubview(self.albumCover) self.addSubview(self.title) self.addSubview(self.artist) let views = ["albumCover": self.albumCover, "title": self.title, "artist": self.artist]; var constraints = Array() constraints.append("H:|-0-[albumCover]-0-|") constraints.append("H:|-0-[title]-0-|") constraints.append("H:|-0-[artist]-0-|") constraints.append("V:|-0-[albumCover]-[title]-[artist]-0-|") let aspectRatioConstraint = NSLayoutConstraint(item: self.albumCover, attribute: .width, relatedBy: .equal, toItem: self.albumCover, attribute: .height, multiplier: 1.0, constant: 0.0) self.addConstraint(aspectRatioConstraint) for constraint in constraints { self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) } for view in self.subviews { view.translatesAutoresizingMaskIntoConstraints = false } } } class AlbumCell : UITableViewCell { var firstAlbumView: AlbumView! var secondAlbumView: AlbumView! override init(style: UITableViewCellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) self.initControls() self.setTheme() self.doLayout() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func initControls() { self.firstAlbumView = AlbumView(frame: CGRect.zero) self.secondAlbumView = AlbumView(frame: CGRect.zero) } func setTheme() { } func doLayout() { self.contentView.addSubview(self.firstAlbumView) self.contentView.addSubview(self.secondAlbumView) let views: [String: AnyObject] = ["firstAlbumView": self.firstAlbumView, "secondAlbumView": self.secondAlbumView]; var constraints = Array() constraints.append("H:|-15-[firstAlbumView(==secondAlbumView)]-15-[secondAlbumView(==firstAlbumView)]-15-|") constraints.append("V:|-15-[firstAlbumView]-15-|") constraints.append("V:|-15-[secondAlbumView]-15-|") for constraint in constraints { self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) } for view in self.contentView.subviews { view.translatesAutoresizingMaskIntoConstraints = false } } } //Details.. class DetailSongViewController : UIViewController { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = UIColor.blue() } /*override func previewActionItems() -> [UIPreviewActionItem] { let regularAction = UIPreviewAction(title: "Regular", style: .default) { (action: UIPreviewAction, vc: UIViewController) -> Void in } let destructiveAction = UIPreviewAction(title: "Destructive", style: .destructive) { (action: UIPreviewAction, vc: UIViewController) -> Void in } let actionGroup = UIPreviewActionGroup(title: "Group...", style: .default, actions: [regularAction, destructiveAction]) return [actionGroup] }*/ } //Implementation.. extension LibraryViewController : UIViewControllerPreviewingDelegate { func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { guard let indexPath = self.tableView.indexPathForRow(at: location) else { return nil } guard let cell = self.tableView.cellForRow(at: indexPath) else { return nil } previewingContext.previewingGestureRecognizerForFailureRelationship.addObserver(self, forKeyPath: "state", options: .new, context: nil) let detailViewController = DetailSongViewController() detailViewController.preferredContentSize = CGSize(width: 0.0, height: 300.0) previewingContext.sourceRect = cell.frame return detailViewController } func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { //self.show(viewControllerToCommit, sender: self) } override func observeValue(forKeyPath keyPath: String?, of object: AnyObject?, change: [NSKeyValueChangeKey : AnyObject]?, context: UnsafeMutablePointer?) { if let object = object { if keyPath == "state" { let newValue = change![NSKeyValueChangeKey.newKey]!.integerValue let state = UIGestureRecognizerState(rawValue: newValue!)! switch state { case .began, .changed: self.navigationItem.title = "Peeking" (object as! UIGestureRecognizer).isEnabled = false case .ended, .failed, .cancelled: self.navigationItem.title = "Not committed" object.removeObserver(self, forKeyPath: "state") DispatchQueue.main.async(execute: { (object as! UIGestureRecognizer).isEnabled = true }) case .possible: break } } } } } class LibraryViewController : UIViewController, UITableViewDelegate, UITableViewDataSource { var tableView: UITableView! override func viewDidLoad() { super.viewDidLoad() self.initControls() self.setTheme() self.registerClasses() self.registerPeekPopPreviews(); self.doLayout() } func initControls() { self.tableView = UITableView(frame: CGRect.zero, style: .grouped) } func setTheme() { self.edgesForExtendedLayout = UIRectEdge() self.tableView.dataSource = self; self.tableView.delegate = self; } func registerClasses() { self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Default") self.tableView.register(AlbumCell.self, forCellReuseIdentifier: "AlbumCell") } func registerPeekPopPreviews() { //if (self.traitCollection.forceTouchCapability == .available) { self.registerForPreviewing(with: self, sourceView: self.tableView) //} } func doLayout() { self.view.addSubview(self.tableView) let views: [String: AnyObject] = ["tableView": self.tableView]; var constraints = Array() constraints.append("H:|-0-[tableView]-0-|") constraints.append("V:|-0-[tableView]-0-|") for constraint in constraints { self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: constraint, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) } for view in self.view.subviews { view.translatesAutoresizingMaskIntoConstraints = false } } func numberOfSections(in tableView: UITableView) -> Int { return 2 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return section == 0 ? 5 : 10 } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return (indexPath as NSIndexPath).section == 0 ? 44.0 : 235.0 } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { return section == 0 ? 75.0 : 50.0 } func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { return 0.0001 } func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { return section == 0 ? "Library" : "Recently Added" } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if (indexPath as NSIndexPath).section == 0 { //Library let cell = tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath) switch (indexPath as NSIndexPath).row { case 0: cell.accessoryType = .disclosureIndicator cell.textLabel?.text = "Playlists" case 1: cell.accessoryType = .disclosureIndicator cell.textLabel?.text = "Artists" case 2: cell.accessoryType = .disclosureIndicator cell.textLabel?.text = "Albums" case 3: cell.accessoryType = .disclosureIndicator cell.textLabel?.text = "Songs" case 4: cell.accessoryType = .disclosureIndicator cell.textLabel?.text = "Downloads" default: break } } if (indexPath as NSIndexPath).section == 1 { //Recently Added let cell = tableView.dequeueReusableCell(withIdentifier: "AlbumCell", for: indexPath) cell.selectionStyle = .none return cell } return tableView.dequeueReusableCell(withIdentifier: "Default", for: indexPath) } } 

实际上这可以使用UIPreviewInteraction API完成。

https://developer.apple.com/documentation/uikit/uipreviewinteraction

它几乎与Peek和Pop API类似。

这里我们有两个阶段:预览和提交,它们对应于后面的API中的Peek和Pop。 我们有UIPreviewInteractionDelegate ,它允许我们访问这些阶段的过渡。

所以应该做的是复制上面的Apple Music Popup,

  • 在didUpdatePreviewTransition期间手动显示模糊叠加

  • 构建上述菜单的xib并在didUpdateCommitTransition期间显示它

  • 您可以在commitTransition阶段结束时使视图停留在那里。

实际上,苹果已经以聊天应用程序的forms构建了这个演示。

从此处下载示例代码并进行测试。