Как мне воспроизвести Apple Music в iOS 10 "Меню действий и просмотра"

iOS 10 имеет функцию, которую я хотел бы повторить. Когда вы касаетесь 3D альбома в приложении Apple Music, открывается меню, показанное ниже. Однако, в отличие от обычного взгляда, он не уходит, когда вы поднимаете палец. Как мне повторить это?

3 ответа

Наиболее близким к нему является следующий код. Он создает фиктивную копию приложения Music. Затем я добавил делегатов PeekPop-3D-Touch.

Однако в делегате я добавляю наблюдателя в распознаватель жестов, а затем отменяю жест при просмотре, но затем снова включаю его, когда палец поднят. Чтобы снова включить его, я сделал это асинхронно, потому что предварительный просмотр немедленно исчезнет без асинхронной отправки. Я не мог найти способ обойти это..

Теперь, если вы нажмете за синее поле, оно исчезнет как обычно =]

https://i.imgur.com/073M2Ku.jpg https://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<String>()

        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<String>()

        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<Void>?) {
        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<String>()

        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

Это почти похоже на API Peek and Pop.

Здесь у нас есть 2 фазы: Preview и Commit, которые соответствуют Peek и Pop в более позднем API. у нас есть UIPreviewInteractionDelegate, который дает нам доступ к переходу через эти фазы.

Итак, что нужно сделать, чтобы скопировать вышеупомянутое Apple Music Popup,

На самом деле, Apple создала демонстрацию этого в форме приложения чата.

Загрузите образец кода отсюда и протестируйте его.

Я написал некоторый код, который будет копироваться, например, в стиле "яблочная музыка" и "поп".

Работа как ниже

объяснение

  • TopView.xib, TopView.swift (вы можете настроить его)
  • PeekAndPopActionView.swift (просмотр для одного действия, такого как загрузка, обмен..)
  • PeekAndPopController.swift (настоящее время, отклонить представление)
  • ForceTouchGestureRecognizer.swift (обнаружение касания силой)

использование

fileprivate let peekedViewController = PeekAndPopController()

@IBAction func presentAction(_ sender: Any) {
    present(peekedViewController, animated: true)
}

let forceTouch = ForceTouchGestureRecognizer()

override func viewDidLoad() {
    super.viewDidLoad()

    forceTouch.addTarget(self, action: #selector(touchAction(_:)))
    forceTouch.cancelsTouchesInView = false
    view.addGestureRecognizer(forceTouch)

    let download = PeekAndPopActionView(text: "Download", image: #imageLiteral(resourceName: "btnDownload"), handler: {
        print("Download Action")
    })

    let playNext = PeekAndPopActionView(text: "Play Next", image: #imageLiteral(resourceName: "btnDownload"), handler: {
        print("Play Next Action")
    })

    let playLast = PeekAndPopActionView(text: "Play Later", image: #imageLiteral(resourceName: "btnDownload"), handler: {
        print("Play Last Action")
    })

    let share = PeekAndPopActionView(text: "Share", image: #imageLiteral(resourceName: "btnDownload"), handler: {
        print("Share Action")
    })

    peekedViewController.addAction(download)
    peekedViewController.addAction(playNext)
    peekedViewController.addAction(playLast)
    peekedViewController.addAction(share)
    peekedViewController.topView = TopView().loadNib()

    peekedViewController.topView?.handler = {
        print("Play Play Play")
    }
}

@objc func touchAction(_ gesture: ForceTouchGestureRecognizer) {
    print(#function, gesture.touch?.location(in: view) ?? "")
    present(peekedViewController, animated: true)
}
Другие вопросы по тегам