web-dev-qa-db-ja.com

UISplitViewControllerで折りたたんだように、SwiftUIでdoubleColumn NavigationViewの詳細を折りたたみますか?

そのため、SwiftUIでリストを作成すると、「無料」のマスター詳細分割ビューが表示されます。

だから例えばこれで:

import SwiftUI

struct ContentView : View {
    var people = ["Angela", "Juan", "Yeji"]

    var body: some View {
        NavigationView {
            List {
                ForEach(people, id: \.self) { person in
                    NavigationLink(destination: Text("Hello!")) {
                        Text(person)
                    }
                }
            }
            Text("????")
        }
    }
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

IPadシミュレーターが横向きで、最初の詳細画面が絵文字の場合、splitViewが表示されます。しかし、名前をタップすると、詳細ビ​​ューは「Hello!」になります。

すばらしいことです。

しかし、iPadを縦向きで実行すると、ユーザーは絵文字で挨拶され、リストがあることを示すものはありません。リストを横から表示するには、左から右にスワイプする必要があります。

ユーザーがタップして左側のアイテムのリストを表示できるようにするナビゲーションバーを表示する方法を知っている人はいますか?絵文字だけの画面にならないように?

「左からスワイプしてファイル/ユーザー/その他のリストを表示する」というメモを残したくない

UISplitViewControllerには、設定可能な折りたたまれたプロパティがあったことを覚えています。ここにそのようなものはありますか?

19
MScottWaller

Xcode 11 beta 3では、Appleが.navigationViewStyle(style:)NavigationViewに追加しました。

enter image description here

Xcode 11 Beta 5向けに更新されました。
createMasterView()DetailsView().

struct MyMasterView: View {

    var people = ["Angela", "Juan", "Yeji"]

    var body: some View {

        List {
            ForEach(people, id: \.self) { person in
                NavigationLink(destination: DetailsView()) {
                    Text(person)
                }
            }
        }

    }
}

struct DetailsView: View {

    var body: some View {
        Text("Hello world")
            .font(.largeTitle)
    }
}

私の内部ContentView

var body: some View {

        NavigationView {

            MyMasterView()

            DetailsView()

        }.navigationViewStyle(DoubleColumnNavigationViewStyle())
         .padding()
    }

出力:

enter image description here

10
Ketan Odedra

現時点では、Xcode 11.2.1では何も変更されていません。 iPadのSplitViewでも同じ問題があり、Ketan Odedra応答のようにパディングを追加することで解決しましたが、少し修正しました。

var body: some View {
    GeometryReader { geometry in
        NavigationView {
            MasterView()
            DetailsView()
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
        .padding(.leading, leadingPadding(geometry))
    }
}

private func leadingPadding(_ geometry: GeometryProxy) -> CGFloat {
    if UIDevice.current.userInterfaceIdiom == .pad {
        return 0.5
    }
    return 0
}

これはシミュレータで完全に機能します。しかし、レビューのためにアプリを送信すると、拒否されました。この小さなハックは、レビュアーデバイスでは機能しません。私は本物のiPadを持っていないので、何が原因なのかわかりません。それを試してください、多分それはあなたのために働くでしょう。

それは私にとってはうまくいきませんが、Apple DTSに助けを求めました。今のところ、SwiftUI APIはUIKitのSplitViewControllerの動作を完全にシミュレートできないとのことです。しかし、回避策:SwiftUIでカスタムSplitViewを作成できます。

struct SplitView<Master: View, Detail: View>: View {
    var master: Master
    var detail: Detail

    init(@ViewBuilder master: () -> Master, @ViewBuilder detail: () -> Detail) {
        self.master = master()
        self.detail = detail()
    }

    var body: some View {
        let viewControllers = [UIHostingController(rootView: master), UIHostingController(rootView: detail)]
        return SplitViewController(viewControllers: viewControllers)
    }
}

struct SplitViewController: UIViewControllerRepresentable {
    var viewControllers: [UIViewController]
    @Environment(\.splitViewPreferredDisplayMode) var preferredDisplayMode: UISplitViewController.DisplayMode

    func makeUIViewController(context: Context) -> UISplitViewController {
        return UISplitViewController()
    }

    func updateUIViewController(_ splitController: UISplitViewController, context: Context) {
        splitController.preferredDisplayMode = preferredDisplayMode
        splitController.viewControllers = viewControllers
    }
}

struct PreferredDisplayModeKey : EnvironmentKey {
    static var defaultValue: UISplitViewController.DisplayMode = .automatic
}

extension EnvironmentValues {
    var splitViewPreferredDisplayMode: UISplitViewController.DisplayMode {
        get { self[PreferredDisplayModeKey.self] }
        set { self[PreferredDisplayModeKey.self] = newValue }
    }
}

extension View {
    /// Sets the preferred display mode for SplitView within the environment of self.
    func splitViewPreferredDisplayMode(_ mode: UISplitViewController.DisplayMode) -> some View {
        self.environment(\.splitViewPreferredDisplayMode, mode)
    }
}

そしてそれを使います:

SplitView(master: {
            MasterView()
        }, detail: {
            DetailView()
        }).splitViewPreferredDisplayMode(.allVisible)

IPadでは動作します。しかし、1つの問題があります(多分もっと..)。 MasterViewとDetailViewの両方にNavigationViewがあるため、このアプローチはiPhoneのナビゲーションを台無しにします。

UPDATE:最後に、Xcode 11.4 Beta 2では、非表示のマスタービューを示すボタンをナビゲーションバーに追加しました。 ????

4
Stanislav K.

シミュレータでのテストは最小限ですが、これは実際のソリューションに近いはずです。アイデアは、EnvironmentObjectを使用して、二重列NavigationStyleを使用するか、単一列を使用するかについて公開された変数を保持し、その変数がある場合にNavigationViewを再作成することです変更。

EnvironmentObject:

  final class AppEnvironment: ObservableObject {
    @Published var useSideBySide: Bool = false
  }

シーンデリゲートで、起動時に変数を設定し、デバイスの回転を観察し、場合によっては変更します(「1000」は正しい値ではありません。開始点)。

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var appEnvironment = AppEnvironment()

    @objc
    func orientationChanged() {
        let bounds = UIScreen.main.nativeBounds
        let orientation = UIDevice.current.orientation

        // 1000 is a starting point, should be smallest height of a + size iPhone
        if orientation.isLandscape && bounds.size.height > 1000 {
            if appEnvironment.useSideBySide == false {
                appEnvironment.useSideBySide = true
                print("SIDE changed to TRUE")
            }
        } else if orientation.isPortrait && appEnvironment.useSideBySide == true {
            print("SIDE changed to false")
            appEnvironment.useSideBySide = false
        }
    }

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        let contentView = ContentView()

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView.environmentObject(appEnvironment))
            self.window = window
            window.makeKeyAndVisible()

            orientationChanged()
            NotificationCenter.default.addObserver(self, selector: #selector(orientationChanged), name: UIDevice.orientationDidChangeNotification, object: nil)
            UIDevice.current.beginGeneratingDeviceOrientationNotifications()
        }

NavigationViewが作成されるトップレベルのコンテンツビューで、navigationViewStyleを直接使用する代わりに、カスタム修飾子を使用します。

struct ContentView: View {
    @State private var dates = [Date]()

    var body: some View {
        NavigationView {
            MV(dates: $dates)
            DetailView()
        }
        .modifier( WTF() )
    }

    struct WTF: ViewModifier {
        @EnvironmentObject var appEnvironment: AppEnvironment

        func body(content: Content) -> some View  {
            Group {
                if appEnvironment.useSideBySide == true {
                    content
                        .navigationViewStyle(DoubleColumnNavigationViewStyle())
                } else {
                    content
                        .navigationViewStyle(StackNavigationViewStyle())
                }
            }
        }
    }
}

先に述べたように、シミュレーターのテストだけですが、マスター表示で回転、詳細表示で回転の両方の方向で起動してみましたが、どれも見栄えがいいです。

0
David H
import SwiftUI

var hostingController: UIViewController?

func showList() {
    let split = hostingController?.children[0] as? UISplitViewController
    UIView.animate(withDuration: 0.3, animations: {
        split?.preferredDisplayMode = .primaryOverlay
    }) { _ in
        split?.preferredDisplayMode = .automatic
    }
}

func hideList() {
    let split = hostingController?.children[0] as? UISplitViewController
    split?.preferredDisplayMode = .primaryHidden
}

// =====

struct Dest: View {
    var person: String

    var body: some View {
        VStack {
            Text("Hello! \(person)")
            Button(action: showList) {
                Image(systemName: "sidebar.left")
            }
        }
        .onAppear(perform: hideList)
    }
}

struct ContentView : View {
    var people = ["Angela", "Juan", "Yeji"]

    var body: some View {
        NavigationView {
            List {
                ForEach(people, id: \.self) { person in
                    NavigationLink(destination: Dest(person: person)) {
                        Text(person)
                    }
                }
            }
            VStack {
                Text("????")
                Button(action: showList) {
                    Image(systemName: "sidebar.left")
                }
            }
        }
    }
}

import PlaygroundSupport
hostingController = UIHostingController(rootView: ContentView())
PlaygroundPage.current.setLiveView(hostingController!)
0
mii