UIKit框架(三十三) —— 替換Peek and Pop交互的基于iOS13的Context Menus(二)

版本記錄

版本號 時間
V1.0 2019.12.28 星期六

前言

iOS中有關視圖控件用戶能看到的都在UIKit框架里面抛人,用戶交互也是通過UIKit進行的棍丐。感興趣的參考上面幾篇文章掘譬。
1. UIKit框架(一) —— UIKit動力學和移動效果(一)
2. UIKit框架(二) —— UIKit動力學和移動效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴張效果的實現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴張效果的實現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復使用的滑塊(二)
7. UIKit框架(七) —— 動態(tài)尺寸UITableViewCell的實現(xiàn)(一)
8. UIKit框架(八) —— 動態(tài)尺寸UITableViewCell的實現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預加載(二)
11. UIKit框架(十一) —— UICollectionView的重用寥掐、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用胀屿、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側滑式面板導航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側滑式面板導航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡單示例(二)
17. UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡單示例(三)
18. UIKit框架(十八) —— 基于CALayer屬性的一種3D邊欄動畫的實現(xiàn)(一)
19. UIKit框架(十九) —— 基于CALayer屬性的一種3D邊欄動畫的實現(xiàn)(二)
20. UIKit框架(二十) —— 基于UILabel跑馬燈類似效果的實現(xiàn)(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定義viewController的轉場和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定義viewController的轉場和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在兩個APP間的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在兩個APP間的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定義布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定義布局 (二)
28. UIKit框架(二十八) —— 一個UISplitViewController的簡單實用示例 (一)
29. UIKit框架(二十九) —— 一個UISplitViewController的簡單實用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的簡單示例(二)
32. UIKit框架(三十二) —— 替換Peek and Pop交互的基于iOS13的Context Menus(一)

源碼

1. Swift

首先看下工程組織結構

接著糊昙,看下sb中的內(nèi)容

Main.storyboard
Map.storyboard
Rating.storyboard

下面就是代碼了

1. SpotsViewController.swift
import UIKit
import MapKit

class SpotsViewController: UITableViewController {
  var vacationSpots: [VacationSpot] = []
  
  // MARK: - Lifecycle
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    vacationSpots = VacationSpot.defaultSpots
  }
  
  // MARK: - Navigation
  
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard
      let selectedCell = sender as? UITableViewCell,
      let selectedRowIndex = tableView.indexPath(for: selectedCell)?.row,
      segue.identifier == "showSpotInfoViewController"
      else {
        fatalError("sender is not a UITableViewCell or was not found in the tableView, or segue.identifier is incorrect")
    }
    
    let vacationSpot = vacationSpots[selectedRowIndex]
    let detailViewController = segue.destination as! SpotInfoViewController
    detailViewController.vacationSpot = vacationSpot
  }
  
  func showMap(vacationSpot: VacationSpot) {
    let storyboard = UIStoryboard(name: "Map", bundle: nil)
    
    let initial = storyboard.instantiateInitialViewController()
    guard
      let navigationController =  initial as? UINavigationController,
      let mapViewController = navigationController.topViewController
        as? MapViewController
      else {
        fatalError("Unexpected view hierarchy")
    }
    
    mapViewController.locationToShow = vacationSpot.coordinate
    mapViewController.title = vacationSpot.name
    
    present(navigationController, animated: true)
  }
  
  // MARK: - UITableViewDataSource
  
  override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return vacationSpots.count
  }
  
  override func tableView(_ tableView: UITableView,
                          cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "VacationSpotCell",
                                             for: indexPath) as! VacationSpotCell
    let vacationSpot = vacationSpots[indexPath.row]
    cell.nameLabel.text = vacationSpot.name
    cell.locationNameLabel.text = vacationSpot.locationName
    cell.thumbnailImageView.image = UIImage(named: vacationSpot.thumbnailName)
    
    return cell
  }
  
  // MARK: - UITableViewDelegate
  
  override func tableView(
    _ tableView: UITableView,
    contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint)
    -> UIContextMenuConfiguration? {
      // 1
      let index = indexPath.row
      let vacationSpot = vacationSpots[index]
      
      // 2
      let identifier = "\(index)" as NSString
      
      return UIContextMenuConfiguration(
      identifier: identifier, previewProvider: nil) { _ in
        // 3
        let mapAction = UIAction(title: "View map",
                                 image: UIImage(systemName: "map")) { _ in
                                  self.showMap(vacationSpot: vacationSpot)
        }
        
        // 4
        let shareAction = UIAction(
          title: "Share",
          image: UIImage(systemName: "square.and.arrow.up")) { _ in
            VacationSharer.share(vacationSpot: vacationSpot, in: self)
        }
        
        // 5
        return UIMenu(title: "", image: nil,
                      children: [mapAction, shareAction])
      }
  }
  
  override func tableView(_ tableView: UITableView,
                          previewForHighlightingContextMenuWithConfiguration
    configuration: UIContextMenuConfiguration)
    -> UITargetedPreview? {
      guard
        // 1
        let identifier = configuration.identifier as? String,
        let index = Int(identifier),
        // 2
        let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0))
          as? VacationSpotCell
        else {
          return nil
      }
      
      // 3
      return UITargetedPreview(view: cell.thumbnailImageView)
  }
  
  override func tableView(
    _ tableView: UITableView, willPerformPreviewActionForMenuWith
    configuration: UIContextMenuConfiguration,
    animator: UIContextMenuInteractionCommitAnimating) {
    // 1
    guard
      let identifier = configuration.identifier as? String,
      let index = Int(identifier)
      else {
        return
    }
    
    // 2
    let cell = tableView.cellForRow(at: IndexPath(row: index, section: 0))
    
    // 3
    animator.addCompletion {
      self.performSegue(withIdentifier: "showSpotInfoViewController",
                        sender: cell)
    }
  }
}
2. SpotInfoViewController.swift
import UIKit
import SafariServices

class SpotInfoViewController: UIViewController {
  var vacationSpot: VacationSpot!
  
  @IBOutlet var backgroundColoredViews: [UIView]!
  @IBOutlet var headingLabels: [UILabel]!
  
  @IBOutlet var ownRatingStackView: UIStackView!
  @IBOutlet var whyVisitLabel: UILabel!
  @IBOutlet var whatToSeeLabel: UILabel!
  @IBOutlet var weatherInfoLabel: UILabel!
  @IBOutlet var averageRatingLabel: UILabel!
  @IBOutlet var ownRatingLabel: UILabel!
  @IBOutlet var weatherHideOrShowButton: UIButton!
  @IBOutlet var submitRatingButton: UIButton!
  
  var shouldHideWeatherInfoSetting: Bool {
    get {
      return UserDefaults.standard.bool(forKey: "shouldHideWeatherInfo")
    }
    set {
      UserDefaults.standard.set(newValue, forKey: "shouldHideWeatherInfo")
    }
  }
  
  var currentUserRating: Int {
    get {
      return UserDefaults.standard.integer(
        forKey: "currentUserRating-\(vacationSpot.identifier)")
    }
    set {
      UserDefaults.standard.set(
        newValue, forKey: "currentUserRating-\(vacationSpot.identifier)")
      updateCurrentRating()
    }
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // Clear background colors from labels and buttons
    for view in backgroundColoredViews {
      view.backgroundColor = .clear
    }
    
    // Set the kerning to 1 to increase spacing between letters
    headingLabels.forEach { $0.attributedText = NSAttributedString(
      string: $0.text!, attributes: [NSAttributedString.Key.kern: 1]) }
    
    title = vacationSpot.name
    
    whyVisitLabel.text = vacationSpot.whyVisit
    whatToSeeLabel.text = vacationSpot.whatToSee
    weatherInfoLabel.text = vacationSpot.weatherInfo
    averageRatingLabel.text = String(repeating: "★", count: vacationSpot.userRating)
    
    updateWeatherInfoViews(hideWeatherInfo: shouldHideWeatherInfoSetting,
                           animated: false)
    
    let interaction = UIContextMenuInteraction(delegate: self)
    submitRatingButton.addInteraction(interaction)
  }
  
  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    updateCurrentRating()
  }
  
  private func updateCurrentRating() {
    UIView.animate(withDuration: 0.3) {
      let rating = self.currentUserRating
      if rating > 0 {
        self.submitRatingButton.setTitle("Update Rating (\(rating))",
          for: .normal)
        self.ownRatingStackView.isHidden = false
        self.ownRatingLabel.text = String(repeating: "★",
                                          count: rating)
      } else {
        self.submitRatingButton.setTitle("Submit Rating", for: .normal)
        self.ownRatingStackView.isHidden = true
      }
    }
  }
  
  @IBAction func weatherHideOrShowButtonTapped(_ sender: UIButton) {
    let shouldHideWeatherInfo = sender.titleLabel!.text! == "Hide"
    updateWeatherInfoViews(hideWeatherInfo: shouldHideWeatherInfo,
                           animated: true)
    shouldHideWeatherInfoSetting = shouldHideWeatherInfo
  }
  
  func updateWeatherInfoViews(hideWeatherInfo shouldHideWeatherInfo: Bool,
                              animated: Bool) {
    let newButtonTitle = shouldHideWeatherInfo ? "Show" : "Hide"
    
    if animated {
      UIView.animate(withDuration: 0.3) {
        self.weatherHideOrShowButton.setTitle(newButtonTitle, for: .normal)
        self.weatherInfoLabel.isHidden = shouldHideWeatherInfo
      }
    } else {
      weatherHideOrShowButton.setTitle(newButtonTitle, for: .normal)
      weatherInfoLabel.isHidden = shouldHideWeatherInfo
    }
  }
  
  @IBAction func wikipediaButtonTapped(_ sender: UIButton) {
    let safariVC = SFSafariViewController(url: vacationSpot.wikipediaURL)
    safariVC.delegate = self
    present(safariVC, animated: true, completion: nil)
  }
  
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.identifier! {
    case "presentMapViewController":
      guard
        let navigationController = segue.destination as? UINavigationController,
        let mapViewController = navigationController.topViewController
          as? MapViewController
        else {
          fatalError("Unexpected view hierarchy")
      }
      mapViewController.locationToShow = vacationSpot.coordinate
      mapViewController.title = vacationSpot.name
    case "presentRatingViewController":
      guard
        let navigationController = segue.destination as? UINavigationController,
        let ratingViewController = navigationController.topViewController
          as? RatingViewController
        else {
          fatalError("Unexpected view hierarchy")
      }
      ratingViewController.vacationSpot = vacationSpot
      ratingViewController.onComplete = updateCurrentRating
    default:
      fatalError("Unhandled Segue: \(segue.identifier!)")
    }
  }
}

// MARK: - SFSafariViewControllerDelegate
extension SpotInfoViewController: SFSafariViewControllerDelegate {
  func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
    controller.dismiss(animated: true, completion: nil)
  }
}

// MARK: - UIContextMenuInteractionDelegate
extension SpotInfoViewController: UIContextMenuInteractionDelegate {
  func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
                              configurationForMenuAtLocation location: CGPoint)
    -> UIContextMenuConfiguration? {
      return UIContextMenuConfiguration(
        identifier: nil,
        previewProvider: makeRatePreview) { _ in
          let removeRating = self.makeRemoveRatingAction()
          let rateMenu = self.makeRateMenu()
          let children = [rateMenu, removeRating]
          return UIMenu(title: "", children: children)
      }
  }
  
  func makeRemoveRatingAction() -> UIAction {
    // 1
    var removeRatingAttributes = UIMenuElement.Attributes.destructive
    
    // 2
    if currentUserRating == 0 {
      removeRatingAttributes.insert(.disabled)
    }
    
    // 3
    let deleteImage = UIImage(systemName: "delete.left")
    
    // 4
    return UIAction(title: "Remove rating",
                    image: deleteImage,
                    identifier: nil,
                    attributes: removeRatingAttributes) { _ in
                      self.currentUserRating = 0
    }
  }
  
  func updateRating(from action: UIAction) {
    guard let number = Int(action.identifier.rawValue) else {
      return
    }
    currentUserRating = number
  }
  
  func makeRateMenu() -> UIMenu {
    let ratingButtonTitles = ["Boring", "Meh", "It's OK", "Like It", "Fantastic!"]
    
    let rateActions = ratingButtonTitles
      .enumerated()
      .map { index, title in
        return UIAction(title: title,
                        identifier: UIAction.Identifier("\(index + 1)"),
                        handler: updateRating)
      }
    
    return UIMenu(title: "Rate...",
                  image: UIImage(systemName: "star.circle"),
                  options: .displayInline,
                  children: rateActions)
  }
  
  func makeRatePreview() -> UIViewController {
    let viewController = UIViewController()
    
    // 1
    let imageView = UIImageView(image: UIImage(named: "rating_star"))
    viewController.view = imageView
    
    // 2
    imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
    imageView.translatesAutoresizingMaskIntoConstraints = false
    
    // 3
    viewController.preferredContentSize = imageView.frame.size
    
    return viewController
  }
}
3. RatingViewController.swift
import UIKit

class RatingViewController: UIViewController {
  var vacationSpot: VacationSpot!
  var onComplete: () -> Void = { }
  
  @IBOutlet var questionLabel: UILabel!
  @IBOutlet var ratingButtons: [UIButton]!
  @IBOutlet var starsStackView: UIStackView!
  @IBOutlet var submitRatingButton: UIButton!
  @IBOutlet var deleteRatingButton: UIButton!
  
  var currentUserRating: Int {
    get {
      return UserDefaults.standard.integer(forKey: "currentUserRating-\(vacationSpot.identifier)")
    }
    set {
      UserDefaults.standard.set(newValue, forKey: "currentUserRating-\(vacationSpot.identifier)")
    }
  }
  
  let ratingButtonTitles = ["Boring", "Meh", "It's OK", "Like It", "Fantastic!"]
  
  // MARK: - Lifecycle
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    // Clear storyboard background colors
    for button in ratingButtons {
      button.backgroundColor = .clear
    }
    
    questionLabel.text = "How would you rate \(vacationSpot.name)?"
    
    showStarCount(currentUserRating, animated: false)
    
    deleteRatingButton.isHidden = currentUserRating == 0
    
    if currentUserRating > 0 {
      submitRatingButton.setTitle("Update Your Rating", for: .normal)
      let index = currentUserRating - 1
      let titleOfButtonToSelect = ratingButtonTitles[index]
      for ratingButton in ratingButtons {
        ratingButton.isSelected = ratingButton.titleLabel!.text! == titleOfButtonToSelect
      }
    }
  }
  
  override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
    super.dismiss(animated: flag, completion: completion)
    onComplete()
  }
  
  // MARK: - Actions
  
  @IBAction func cancelButtonTapped(_ sender: UIBarButtonItem) {
    dismiss(animated: true, completion: nil)
  }
  
  @IBAction func submitRatingTapped() {
    currentUserRating = starsStackView.arrangedSubviews.count
    dismiss(animated: true, completion: nil)
  }
  
  @IBAction func deleteRatingTapped() {
    currentUserRating = 0
    dismiss(animated: true, completion: nil)
  }
  
  @IBAction func ratingButtonTapped(_ sender: UIButton) {
    let buttonTitle = sender.titleLabel!.text!
    
    // Select the tapped button and unselect others
    for ratingButton in ratingButtons {
      ratingButton.isSelected = ratingButton == sender
    }
    
    let rating = ratingForButtonTitle(buttonTitle)
    showStarCount(rating)
  }
  
  // MARK: - Helper Methods
  
  func ratingForButtonTitle(_ buttonTitle: String) -> Int {
    guard let index = ratingButtonTitles.firstIndex(of: buttonTitle) else {
      fatalError("Rating not found for buttonTitle: \(buttonTitle)")
    }
    return index + 1
  }
  
  func showStarCount(_ totalStarCount: Int, animated: Bool = true) {
    let starsToAdd = totalStarCount - starsStackView.arrangedSubviews.count
    
    if starsToAdd > 0 {
      for _ in 1...starsToAdd {
        let starImageView = UIImageView(image: UIImage(named: "rating_star"))
        starImageView.contentMode = .scaleAspectFit
        starImageView.frame.origin = CGPoint(x: starsStackView.frame.width, y: 0) // animate in from the right
        starsStackView.addArrangedSubview(starImageView)
      }
    } else if starsToAdd < 0 {
      let starsToRemove = abs(starsToAdd)
      
      for _ in 1...starsToRemove {
        guard let star = starsStackView.arrangedSubviews.last else {
          fatalError("Unexpected Logic Error")
        }
        star.removeFromSuperview() // No need to call removeArrangedSubview separately
      }
    }
    
    if animated {
      UIView.animate(withDuration: 0.25) {
        self.starsStackView.layoutIfNeeded()
      }
    }
  }
}
4. MapViewController.swift
import UIKit
import MapKit

class MapViewController: UIViewController {
  var locationToShow: CLLocationCoordinate2D!

  @IBOutlet var mapView: MKMapView!

  override func viewDidLoad() {
    super.viewDidLoad()

    mapView.setCenter(locationToShow, animated: true)

    let zoomRegion = MKCoordinateRegion.init(center: locationToShow, latitudinalMeters: 15000, longitudinalMeters: 15000)
    mapView.setRegion(zoomRegion, animated: true)

    let annotation = MKPointAnnotation()
    annotation.coordinate = locationToShow
    mapView.addAnnotation(annotation)
  }

  @IBAction func doneButtonTapped(_ sender: UIBarButtonItem) {
    dismiss(animated: true, completion: nil)
  }
}
5. VacationSpot.swift
import Foundation
import MapKit

struct VacationSpot {
  let identifier: Int
  let name: String
  let locationName: String
  let thumbnailName: String
  let whyVisit: String
  let whatToSee: String
  let weatherInfo: String
  let userRating: Int
  let wikipediaURL: URL
  let coordinate: CLLocationCoordinate2D
}

extension VacationSpot: Codable {
  enum CodingKeys: String, CodingKey {
    case identifier
    case name
    case locationName
    case thumbnailName
    case whyVisit
    case whatToSee
    case weatherInfo
    case userRating
    case wikipediaLink
    case latitude
    case longitude
  }
  
  public func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(identifier, forKey: .identifier)
    try container.encode(name, forKey: .name)
    try container.encode(locationName, forKey: .locationName)
    try container.encode(thumbnailName, forKey: .thumbnailName)
    try container.encode(whyVisit, forKey: .whyVisit)
    try container.encode(whatToSee, forKey: .whatToSee)
    try container.encode(weatherInfo, forKey: .weatherInfo)
    try container.encode(userRating, forKey: .userRating)
    try container.encode(wikipediaURL, forKey: .wikipediaLink)
    try container.encode(coordinate.latitude, forKey: .latitude)
    try container.encode(coordinate.longitude, forKey: .longitude)
  }
  
  public init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    
    identifier = try values.decode(Int.self, forKey: .identifier)
    name = try values.decode(String.self, forKey: .name)
    locationName = try values.decode(String.self, forKey: .locationName)
    thumbnailName = try values.decode(String.self, forKey: .thumbnailName)
    whyVisit = try values.decode(String.self, forKey: .whyVisit)
    whatToSee = try values.decode(String.self, forKey: .whatToSee)
    weatherInfo = try values.decode(String.self, forKey: .weatherInfo)
    userRating = try values.decode(Int.self, forKey: .userRating)
    
    let wikipediaLink = try values.decode(String.self, forKey: .wikipediaLink)
    guard let wikiURL = URL(string: wikipediaLink) else {
      fatalError("Invalid Wikipedia URL.")
    }
    wikipediaURL = wikiURL
      
    let latitude = try values.decode(Double.self, forKey: .latitude)
    let longitude = try values.decode(Double.self, forKey: .longitude)
    coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
  }
}

// MARK: - Support for loading data from plist

extension VacationSpot {
  static let defaultSpots = loadVacationSpotsFromPlistNamed("vacation_spots")
  
  static func loadVacationSpotsFromPlistNamed(_ plistName: String) -> [VacationSpot] {
    guard
      let plistURL = Bundle.main.url(forResource: plistName, withExtension: "plist"),
      let data = try? Data(contentsOf: plistURL)
      else {
        fatalError("An error occurred while reading \(plistName).plist")
    }
    
    let decoder = PropertyListDecoder()
    
    do {
      let vacationSpots = try decoder.decode([VacationSpot].self, from: data)
      return vacationSpots
    } catch {
      print("Couldn't load vacation spots: \(error.localizedDescription)")
      return []
    }
  }
}
6. VacationSpotCell.swift
import UIKit

class VacationSpotCell: UITableViewCell {
  @IBOutlet var nameLabel: UILabel!
  @IBOutlet var locationNameLabel: UILabel!
  @IBOutlet var thumbnailImageView: UIImageView!

  override func awakeFromNib() {
    super.awakeFromNib()

    // 1
    let layoutGuide = UILayoutGuide()
    contentView.addLayoutGuide(layoutGuide)

    // 2
    let topConstraint = layoutGuide.topAnchor
      .constraint(equalTo: nameLabel.topAnchor)

    // 3
    let bottomConstraint = layoutGuide.bottomAnchor
      .constraint(equalTo: locationNameLabel.bottomAnchor)

    // 4
    let centeringConstraint = layoutGuide.centerYAnchor
      .constraint(equalTo: contentView.centerYAnchor)

    // 5
    NSLayoutConstraint.activate(
      [topConstraint, bottomConstraint, centeringConstraint])
  }
}
7. VacationSharer.swift
import UIKit

struct VacationSharer {
  static func share(vacationSpot: VacationSpot,
                    in viewController: UIViewController) {
    let text = """
    You should really visit \(vacationSpot.name)!
    
    \(vacationSpot.whyVisit)
    
    \(vacationSpot.whatToSee)
    """
    
    guard let image = UIImage(named: vacationSpot.thumbnailName) else {
      return
    }
    
    let activityViewController = UIActivityViewController(
      activityItems: [text, image], applicationActivities: nil)
    
    viewController.present(activityViewController, animated: true, completion: nil)
  }
  
}

后記

本篇主要講述了替換舊的Peek and Pop交互的基于iOS13的Context Menus费奸,感興趣的給個贊或者關注~~~

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末弥激,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子愿阐,更是在濱河造成了極大的恐慌微服,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缨历,死亡現(xiàn)場離奇詭異以蕴,居然都是意外死亡糙麦,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門丛肮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赡磅,“玉大人,你說我怎么就攤上這事宝与》倮龋” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵习劫,是天一觀的道長咆瘟。 經(jīng)常有香客問我,道長诽里,這世上最難降的妖魔是什么袒餐? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮须肆,結果婚禮上匿乃,老公的妹妹穿的比我還像新娘。我一直安慰自己豌汇,他們只是感情好幢炸,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拒贱,像睡著了一般宛徊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逻澳,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天闸天,我揣著相機與錄音,去河邊找鬼斜做。 笑死苞氮,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瓤逼。 我是一名探鬼主播笼吟,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霸旗!你這毒婦竟也來了贷帮?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤诱告,失蹤者是張志新(化名)和其女友劉穎撵枢,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡锄禽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年潜必,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沟绪。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡刮便,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绽慈,到底是詐尸還是另有隱情,我是刑警寧澤辈毯,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布坝疼,位于F島的核電站,受9級特大地震影響谆沃,放射性物質(zhì)發(fā)生泄漏钝凶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一唁影、第九天 我趴在偏房一處隱蔽的房頂上張望耕陷。 院中可真熱鬧,春花似錦据沈、人聲如沸哟沫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嗜诀。三九已至,卻和暖如春孔祸,著一層夾襖步出監(jiān)牢的瞬間隆敢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工崔慧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拂蝎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓惶室,卻偏偏與公主長得像温自,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拇涤,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容