Vincent Tourraine
Blog

Notes WWDC 2022 : Desktop-class iPad apps

#dev #iOS

Mes notes pour les sessions Build a desktop-class iPad app et Adopt desktop-class editing interactions de la WWDC 2022.

UI organization

navigationItem.style = .editor
navigationItem.backAction = UIAction(...)

Title menu for actions affecting the whole document.

let properties = UIDocumentProperties(url: document.fileURL)

if let itemProvider = NSItemProvider(contentsOf: document.fileURL) {
  properties.dragItemsProvider = { _ in
    [UIDragItem(itemProvider: itemProvider)]
  }
  properties.activityViewControllerProvider = {
    UIActivityViewController(activityItems: [itemProvider], applicationActivities: nil)
  }
}

navigationItem.documentProperties = properties

Add custom actions (not automatically included on Catalyst, need to explicitly add them to app menu):

func didOpenDocument() {
  ...
  navigationItem.titleMenuProvider = { [unowned self] suggested in
    var children = suggested
    children += [
      UIMenu(title: "Export…", image: UIImage(systemName: "arrow.up.forward.square"), children: [
        UIAction(title: "HTML", image: UIImage(systemName: "safari")) { ... },
        UIAction(title: "PDF", image: UIImage(systemName: "doc")) { ... }
      ])
    ]
    return UIMenu(children: children)
  }
}

Customizable center items:

navigationItem.customizationIdentifier = "editorView"
(...)
UIBarButtonItem(title: "Sync Scrolling", ...).creatingFixedGroup()
UIBarButtonItem(title: "Add Link", ...).creatingOptionalGroup(customizationIdentifier: "addLink")
UIBarButtonItemGroup.optionalGroup(customizationIdentifier: "textFormat", isInDefaultCustomization: false, representativeItem: UIBarButtonItem(title: "Format", ...), items: [
UIBarButtonItem(title: "Bold", ...),
UIBarButtonItem(title: "Italics", ...),
UIBarButtonItem(title: "Underline", ...)])

Quick actions

Best with lightweight selection.

collectionView.allowsMultipleSelection = true
collectionView.allowsFocus = true
collectionView.selectionFollowsFocus = true

(...)

func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemsAt indexPaths: [IndexPath], point: CGPoint) -> UIContextMenuConfiguration? {

  if indexPaths.count == 0 {
    // Construct an empty space menu
  } 
  else if indexPaths.count == 1 {
    // Construct a single item menu
  } 
  else {
    // Construct a multi-item menu
  }
}

Text experience

textView.isFindInteractionEnabled = true

Custom actions:

func textView(_ textView: UITextView, editMenuForTextIn range: NSRange, suggestedActions: [UIMenuElement]) -> UIMenu? {

  if textView.selectedRange.length > 0 {
    let customActions = [ UIAction(title: "Hide", ... ) { ... } ]
    return UIMenu(children: customActions + suggestedActions)
  }

  return nil
}

Presenting programmatically (not supported on Mac idiom):

let editMenuInteraction = UIEditMenuInteraction(delegate: self)
view.addInteraction(editMenuInteraction)

let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))
tapRecognizer.allowedTouchTypes = [UITouch.TouchType.direct.rawValue as NSNumber]
view.addGestureRecognizer(tapRecognizer)

@objc func didTap(_ recognizer: UITapGestureRecognizer) {
  let location = recognizer.location(in: self.view)
  if self.hasSelectedObjectView(at: location) {
    let configuration = UIEditMenuConfiguration(identifier: nil, sourcePoint: location)
    editMenuInteraction.presentEditMenu(with: configuration)
  }
}

func editMenuInteraction(_ interaction: UIEditMenuInteraction, targetRectFor configuration: UIEditMenuConfiguration) -> CGRect {
  guard let selectedView = objectView(at: configuration.sourcePoint) else {
    return .null
  }

  return selectedView.frame
}

func editMenuInteraction(_ interaction: UIEditMenuInteraction, menuFor configuration: UIEditMenuConfiguration, suggestedActions: [UIMenuElement]) -> UIMenu? {
  let duplicateAction = UIAction(title: "Duplicate") { ... }
  return UIMenu(children: suggestedActions + [duplicateAction])
}

Can be kept presented:

UIAction(title: "Increase", image: UIImage(systemName: "increase.indent"), attributes: .keepsMenuPresented) { ... }