web-dev-qa-db-ja.com

SwiftUIリストを新しい選択までスクロール

単一選択が可能なSwiftUIリストがある場合は、リストをクリックして(おそらく、これによりキーレスポンダーになる)、矢印キーを使用して選択を変更できます。その選択範囲が表示領域の最後に到達すると、リスト全体がスクロールされ、選択範囲が表示されたままになります。

ただし、選択オブジェクトが他の方法(ボタンなど)で更新された場合、リストはスクロールされません。

プログラムで設定したときに、リストを強制的に新しい選択にスクロールする方法はありますか?

アプリの例:

import SwiftUI

struct ContentView: View {
    @State var selection: Int? = 0

    func changeSelection(_ by: Int) {
        switch self.selection {
        case .none:
            self.selection = 0
        case .some(let sel):
            self.selection = max(min(sel + by, 20), 0)
        }
    }

    var body: some View {
        HStack {
            List((0...20), selection: $selection) {
                Text(String($0))
            }
            VStack {
                Button(action: { self.changeSelection(-1) }) {
                    Text("Move Up")
                }
                Button(action: { self.changeSelection(1) }) {
                    Text("Move Down")
                }
            }
        }
    }
}
14
Brandon Horst

私はそれをこのようにしています:

1)再利用可能なコピーペーストコンポーネント:

import SwiftUI

struct TableViewConfigurator: UIViewControllerRepresentable {

    var configure: (UITableView) -> Void = { _ in }

    func makeUIViewController(context: UIViewControllerRepresentableContext<TableViewConfigurator>) -> UIViewController {

        UIViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TableViewConfigurator>) {

        //let tableViews = uiViewController.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()

        let tableViews = UIApplication.nonModalTopViewController()?.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()

        for tableView in tableViews {
            self.configure(tableView)
        }
    }
}

2)階層内のトップビューコントローラーを見つけるUIApplicationの拡張

拡張UIApplication {

class var activeSceneRootViewController: UIViewController? {

    if #available(iOS 13.0, *) {
        for scene in UIApplication.shared.connectedScenes {
            if scene.activationState == .foregroundActive {
                return ((scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate)?.window??.rootViewController
            }
        }

    } else {
        // Fallback on earlier versions
        return UIApplication.shared.keyWindow?.rootViewController
    }

    return nil
}

class func nonModalTopViewController(controller: UIViewController? = UIApplication.activeSceneRootViewController) -> UIViewController? {

        print(controller ?? "nil")

        if let navigationController = controller as? UINavigationController {
            return nonModalTopViewController(controller: navigationController.topViewController ?? navigationController.visibleViewController)
        }
        if let tabController = controller as? UITabBarController {
            if let selected = tabController.selectedViewController {
                return nonModalTopViewController(controller: selected)
            }
        }
        if let presented = controller?.presentedViewController {
            let top = nonModalTopViewController(controller: presented)
            if top == presented { // just modal
                return controller
            } else {
                print("Top:", top ?? "nil")
                return top
            }
        }

        if let navigationController = controller?.children.first as? UINavigationController {

            return nonModalTopViewController(controller: navigationController.topViewController ?? navigationController.visibleViewController)
        }

        return controller
        }
}

3)カスタムパーツ-ここでは、スクロールのようなリストの背後にあるUITableViewのソリューションを実装します。

リスト内のビューの任意のビューで修飾子のように使用します

.background(TableViewConfigurator(configure: { tableView in
                if self.viewModel.statusChangeMessage != nil {
                    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
                        let lastIndexPath = IndexPath(row: tableView.numberOfRows(inSection: 0) - 1, section: 0)
                        tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
                    }
                }
            }))
1
Michał Ziobro