WWDC Quick Look 💓 By SwiftGGTeam
Build a desktop-class iPad app

Build a desktop-class iPad app

元の動画を見る

ハイライト

このセッションでは、例として Markdown エディターを使用して、iPadOS 15 アプリをデスクトップレベルのエクスペリエンスにアップグレードする方法を完全にデモンストレーションします。主な変更点は、ナビゲーション バー、複数選択インタラクション、テキスト編集の 3 つの領域に焦点を当てています。


主要内容

このデモは iPadOS 15 Markdown エディターから始まります。このアプリは、ナビゲーション バーによく使用されるボタンを配置し、メニューやポップアップ ウィンドウに二次的な機能を配置しました。問題は、ユーザーがキーボードやトラックパッドを Mac Catalyst に接続したり、アプリを実行したりするときに、より高密度で、よりカスタマイズ可能で、より直接的な操作を期待していることです。

Apple は、iPadOS 16 の UIKit に、ナビゲーション バー スタイル、タイトル メニュー、カスタマイズ可能なセンター ツール項目、複数選択メニュー、検索と置換、および編集メニューなど、デスクトップ レベルの API のいくつかのセットを追加しました。デモのルートは非常に明確です。最初にドキュメント操作をナビゲーション バーに移動し、次にツールバーをカスタマイズ可能にし、次にサイドバーで軽量の複数選択をサポートし、最後にテキスト編集を強化します。

これは視覚的なリスキンではありません。各ステップには、対応するインタラクションの問題があります。文書情報があまりにも深く隠蔽されている、バッチ処理では編集モードに入る必要がある、テキスト検索は自分で行う必要があり、Mac Catalyst のメニューもシステムの習慣に適応する必要があります。 UIKit の新しい API は、これらの問題を解決します。


詳細

エディターのナビゲーション スタイルを選択します

(03:34) iPadOS 16 では、ナビゲーション バーのスタイルがナビゲーター、ブラウザー、エディターに分割されています。 Navigator は階層データに適しており、Browser はファイルおよびドキュメントの参照に適しており、Editor は単一ドキュメントの表示または編集に重点を置くのに適しています。この Markdown エディターは単一のドキュメントを編集しているため、[エディター] を選択します。

navigationItem.style = .editor

キーポイント:

  • navigationItem現在のビュー コントローラーのナビゲーション項目です。 -styleナビゲーション バーがどのデスクトップ レベルのレイアウトを採用するかを制御します。 -.editorタイトルが左側に配置され、ナビゲーションバーの中央領域が解放され、その後ろにツールアイテムを配置できます。

(03:52) 元の末尾の [完了] ボタンは、標準の戻るアクションに変更できます。デモンストレーション用backAction「ドキュメントセレクターに戻る」動作を表現します。

navigationItem.backAction = UIAction()

キーポイント:

  • backActionナビゲーション バーの戻るボタンによってトリガーされるアクションを定義します。 -UIAction閉じるロジックをラップするか、ドキュメント閲覧インターフェイスに戻ります。
  • これにより、追加の「完了」ボタンを持たずに、リターンエントリでシステムの標準の外観を使用できるようになります。

タイトル メニューを使用してドキュメント情報とドキュメント操作を実行します。

(04:48) タイトル メニューはナビゲーション バーのタイトルから開き、ドキュメントのメタデータやドキュメント全体に影響する操作に適しています。サンプルアプリは次のとおりですUIDocument裏付けられているため、以下から入手できます。fileURL作成するUIDocumentProperties

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

キーポイント:

  • UIDocumentProperties(url:)ドキュメントURLを使用して、タイトルメニューのドキュメントヘッダ情報を作成します。 -NSItemProvider(contentsOf:)同じファイル URL に対してドラッグ可能で共有可能なデータ プロバイダーを作成します。 -dragItemsProvider戻るUIDragItem, ユーザーはタイトル メニューのドキュメント アイコンをドラッグして、ドキュメントをアプリの外にコピーできます。 -activityViewControllerProvider戻るUIActivityViewController、タイトルメニューの共有ボタンをクリックすると、システム共有インターフェイスが開きます。 -navigationItem.documentPropertiesこの一連のドキュメント情報をナビゲーション バーのタイトル メニューに接続します。

(06:36) 名前の変更もタイトル メニューの一部です。システムには組み込みの名前変更 UI が提供されますが、データ モデルの実際の変更は依然としてアプリによって行われます。

override func viewDidLoad() {
    navigationItem.renameDelegate = self
}

func navigationItem(_ navigationItem: UINavigationItem, didEndRenamingWith title: String) {
    // Rename document using methods appropriate to the app’s data model
}

キーポイント:

  • 存在するviewDidLoad()現在のオブジェクトを次のように設定しますrenameDelegate
  • プロキシを設定した後、システムによって提供される名前変更アクションがタイトル メニューに表示されるようになります。 -didEndRenamingWith titleユーザーが新しいタイトルを送信した後に呼び出されます。
  • コメント内の名前変更ロジックは、アプリ独自のデータ モデルまたはファイル システム操作に接続する必要があります。

(07:09) タイトル メニューには、システム推奨アクションとアプリのカスタム アクションという 2 種類のアクションがあります。システム アクションにはローカライズされたタイトルと SF シンボル アイコンがあります。カスタム アクションは、エクスポートなどのビジネス機能に適しています。

override func duplicate(_ sender: Any?) {
    // Duplicate document
}

override func move(_ sender: Any?) {
    // Move document
}

func didOpenDocument() {
    ...
    navigationItem.titleMenuProvider = { [unowned self] suggested in
        var children = suggested

        ...

        return UIMenu(children: children)
    }
}

キーポイント:

  • duplicate(_:)そしてmove(_:)システムアクションに相当するメソッドです。
  • UIKit は、利用可能なシステム アクションをtitleMenuProvidersuggestedパラメータ。 -children = suggestedシステムの提案を保持します。
  • 戻るUIMenu(children:)その後、タイトル メニューにこれらのアクションが表示されます。
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)
    }
}

キーポイント:

  • children += [...]システム提案の後にカスタム メニューを追加します。 -UIMenu(title:image:children:)「エクスポート」サブメニューを作成します。
  • UIActionそれぞれ HTML と PDF のエクスポートを表します。
  • デモのハイライト:titleMenuProviderMac Catalyst では呼び出されません。カスタム アクションを手動で追加する必要がありますmain UIMenuSystemそうして初めて、Mac の [ファイル] メニューに入ります。

よく使用するツールをカスタマイズ可能な中央エリアに配置

(09:35) エディター スタイルにより、ナビゲーション バーの中央領域が解放されます。この領域は、同期スクロール、リンクの挿入、テキストの書式設定など、もともとメニューに隠されていたツールに適しています。ユーザーのカスタマイズを有効にするには、まず次のコマンドを入力します。navigationItem永続的な識別子。

navigationItem.customizationIdentifier = "editorView"

キーポイント:

  • customizationIdentifierナビゲーションバーの中央項目のカスタマイズ機能を有効にします。
  • 文字列は安定している必要があり、ユーザーのツールバー構成を保存するためにシステムによって使用されます。
  • 構成が相互に上書きされるのを避けるために、同じアプリ内の異なるインターフェイスは異なる識別子を使用する必要があります。

(10:00) 欠如できない関数は、固定グループ化に適しています。この例の同期スクロールには他にエントリがないため、固定グループに設定されています。

UIBarButtonItem(title: "Sync Scrolling", ...).creatingFixedGroup()

キーポイント:

  • UIBarButtonItem単一のツール ボタンを作成します。 -creatingFixedGroup()ボタンを固定としてラップしますUIBarButtonItemGroup
  • 固定グループは、ユーザーが削除または移動することはできません。

(10:23) 置き換え可能な、重要ではない機能は、オプションのグループに当てはまります。リンクの追加はMarkdownリンクを手書きで行えるので、カスタマイズ可能な項目を入れてください。

UIBarButtonItem(title: "Add Link", ...).creatingOptionalGroup(customizationIdentifier: "addLink")

キーポイント:

  • creatingOptionalGroupユーザーが移動可能で取り外し可能なツール アイテムを作成します。 -customizationIdentifier: "addLink"このボタンのカスタム状態を保持するために使用されます。
  • このようなボタンはデフォルトで表示することも、ユーザーがツールバーから削除することもできます。

(10:56) 優先度の低いツールはカスタム パネルに表示できますが、デフォルトでは表示されません。この例の「形式」グループには、太字、斜体、下線が含まれています。

UIBarButtonItemGroup.optionalGroup(customizationIdentifier: "textFormat",
                                   isInDefaultCustomization: false,
                                   representativeItem: UIBarButtonItem(title: "Format", ...)
                                   items: [
                                        UIBarButtonItem(title: "Bold", ...),
                                        UIBarButtonItem(title: "Italics", ...),
                                        UIBarButtonItem(title: "Underline", ...),
                                   ])

キーポイント:

  • optionalGroupオプションのボタングループを直接作成します。 -isInDefaultCustomization: falseデフォルトのツールバーに表示されないことを示します。 -representativeItemカスタム パネルとコンパクトな状態にデリゲート ボタンを提供します。 -items実際にアクションを実行するのはグループ内のボタンです。

(13:16) ナビゲーション バーに十分なスペースがない場合、UIKit はオーバーフロー メニューに配置できない中央の項目を配置します。通常のボタンは自動的にメニュー表現に変換できますが、カスタム ビューを持つボタンは独自のメニュー表現を提供する必要があります。

sliderGroup.menuRepresentation = UIMenu(title: "Text Size",
                                        preferredElementSize: .small,
                                        children: [
    UIAction(title: "Decrease",
             image: UIImage(systemName: "minus.magnifyingglass"),
             attributes: .keepsMenuPresented) { ... },
    UIAction(title: "Reset",
             image: UIImage(systemName: "1.magnifyingglass"),
             attributes: .keepsMenuPresented) { ... },
    UIAction(title: "Increase",
             image: UIImage(systemName: "plus.magnifyingglass"),
             attributes: .keepsMenuPresented) { ... },
])

キーポイント:

  • menuRepresentationオーバーフロー メニューに入った後のボタン グループの表示方法を指定します。 -UIMenu(title:preferredElementSize:children:)コンパクトなメニューを作成します。 -.smalliPad のメニュー項目をよりコンパクトな横長形式で表示します。 -.keepsMenuPresented「減少」、「リセット」、「増加」の実行後にメニューを閉じないようにすることで、ユーザーは継続的に文字サイズを調整できます。

軽量の複数選択を使用して編集モードを置き換えます

(15:10) サンプル アプリのサイドバーはディレクトリのリストです。 iOS 16 より前では、バッチ編集には通常、別の編集モードに入り、ツールバーにバッチ操作を配置する必要がありました。 iPadOS 16 の複数選択メニューは、デスクトップレベルの操作により適しています。ユーザーは直接複数の選択を行い、選択した項目のメニューを開きます。

// Enable multiple selection
collectionView.allowsMultipleSelection = true

// Enable keyboard focus
collectionView.allowsFocus = true

// Allow keyboard focus to drive selection 
collectionView.selectionFollowsFocus = true

キーポイント:

  • allowsMultipleSelection = trueコレクション ビューで複数のアイテムを同時に選択できるようにします。 -allowsFocus = trueキーボードのフォーカスをコレクション ビューに置きます。 -selectionFollowsFocus = true外部キーボードのシナリオに適した、キーボードのフォーカスによる選択の駆動を可能にします。

(16:11) 複数選択をオンにすると、古いdidSelectItemAtIndexPath選択ごとに起動され、エディターが対応するタイトルまでスクロールします。解決策は、「タイトルにジャンプ」ロジックを新しいプライマリ アクション メソッドに移動することです。

func collectionView(_ collectionView: UICollectionView, 
                    performPrimaryActionForItemAt indexPath: IndexPath) {
  
    // Scroll to the tapped element
    if let element = dataSource.itemIdentifier(for: indexPath) {
        delegate?.outline(self, didChoose: element)
    }
}

キーポイント:

  • performPrimaryActionForItemAt単一の項目に対するユーザーの主な操作のみを処理します。 -dataSource.itemIdentifier(for:)インデックス パスから対応するディレクトリ要素を見つけます。 -delegate?.outline(self, didChoose:)対応するコンテンツまでスクロールするビジネスロジックを実行します。
  • 複数の項目が選択されている場合、このジャンプ ロジックはトリガーされなくなりました。

(16:56) iPadOS 16 では、古い単一項目のコンテキスト メニュー メソッドが新しい複数項目メニュー メソッドに置き換えられます。メソッドパラメータ内indexPaths量によってメニューの種類が決まります。

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
    }
}

キーポイント:

  • indexPaths.count == 0ユーザーが空白領域でメニューを開くことを示します。 -indexPaths.count == 1メニューが単一の項目に対して操作されることを示します。 -indexPaths.count > 1メニューが選択された複数の項目に対して動作することを示します。
  • 同じデリゲート メソッドは、空白領域、単一項目、および複数項目の 3 つのメニュー エントリをカバーします。

システムの検索と置換およびテキスト編集メニューをオンにします

(18:12) サンプルアプリの使用方法UITextView編集者は、検索と置換の動作をカスタマイズする必要はありません。システムのデフォルトの検索と置換では、1 つの属性を開くだけで済みます。

textView.isFindInteractionEnabled = true

キーポイント:

  • textViewMarkdownテキスト編集領域です。 -isFindInteractionEnabled = trueシステムの検索と置換エクスペリエンスをオンにします。
  • 設定すると、ユーザーはテキストの編集中に Command-F を押して検索と置換 UI を開くことができます。

(18:34) アプリがテキスト編集メニューにビジネス アクションを追加する必要がある場合、新しいアクションを実装できます。UITextViewDelegate方法。この例では、ユーザーがテキストを選択したときに非表示アクションを追加します。

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
}

キーポイント:

  • editMenuForTextIn rangeシステムがテキスト編集メニューを表示する準備をしているときに呼び出されます。 -suggestedActionsシステムが推奨するコピー&ペーストなどのメニュー項目です。 -selectedRange.length > 0ユーザーがテキストを選択した場合にのみ非表示が追加されるようにします。 -customActions + suggestedActionsカスタム アクションをシステム アクションの前に置きます。
  • 戻るnilの場合、システムはデフォルトの編集メニューを使用します。

重要ポイント

  1. やるべきこと: タイトル メニューをドキュメント アプリに追加します

    • 実行する価値がある理由: タイトル メニューは、ドキュメント情報の伝達、ドラッグ アンド ドロップ、共有、名前変更、コピーと移動を同時に行うことができ、ドキュメント エディター、メモ、描画ツールに適しています。
    • 開始方法:からUIDocumentProperties(url:)始める、置くnavigationItem.documentProperties既存のドキュメント URL を取得します。再利用renameDelegate名前の変更にアクセスします。
  2. 対処方法: 非表示のツールをカスタマイズ可能なナビゲーション バーの中央に移動します

    • 実行する価値がある理由: ユーザーは、高頻度の操作をツールバーに残し、低頻度の操作をカスタム パネルまたはオーバーフロー メニューに移動できます。
    • 開始方法: 最初にセットアップしますnavigationItem.style = .editorそしてcustomizationIdentifier、そして既存のUIBarButtonItem固定グループ、オプション グループ、デフォルト以外のオプション グループに分かれています。
  3. やるべきこと: サイドバーのバッチ操作を軽量の複数選択メニューに変更します

    • 実行する価値がある理由: ユーザーは、編集モードに入ることなく、複数のディレクトリ項目、ファイル、タグ、またはタスクに対してバッチ操作を実行できます。
    • 開始方法: 有効にするallowsMultipleSelectionallowsFocusselectionFollowsFocus、クリック ジャンプ ロジックをに移行します。collectionView(_:performPrimaryActionForItemAt:)
  4. やるべきこと: システムの検索と置換をテキスト エディタに追加します

    • 実行する価値がある理由: Command-F はデスクトップ ユーザーにとって筋肉の記憶であり、システムの検索と置換は通常の操作を直接オーバーライドできます。UITextViewシーン。
    • 開始方法: 最初にセットアップしますtextView.isFindInteractionEnabled = true;検索エクスペリエンスをカスタマイズする必要がある場合は、引き続き調査してくださいUIFindInteraction
  5. 対処方法: Mac Catalyst メニュー マッピングを確認します

    • 実行する価値がある理由: システム タイトル メニューのアクションは Mac の [ファイル] メニューに入力されますが、カスタム タイトル メニューのアクションは自動的には入力されず、個別に処理する必要があります。
    • 開始方法: まず、複製、移動、名前変更などのシステム アクションを接続します。次に、エクスポートなどのカスタム アクションを渡します。main UIMenuSystemMacのメニューに追加します。

関連セッション

コメント

GitHub Issues · utterances