web-dev-qa-db-fra.com

Réduire un détail DoubleColumn NavigationView dans SwiftUI comme avec réduit sur UISplitViewController?

Ainsi, lorsque je fais une liste dans SwiftUI, j'obtiens la vue partagée maître-détail "gratuitement".

Ainsi, par exemple, avec ceci:

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

J'obtiens un splitView si un simulateur iPad est en paysage, et le premier écran de détail est l'émoji. Mais si les gens tapent sur un nom, la vue détaillée est "Bonjour!"

Tout ça c'est super.

Cependant, si je lance l'iPad en mode portrait, l'utilisateur est accueilli par les emoji, et rien n'indique qu'il existe une liste. Vous devez balayer de gauche à droite pour faire apparaître la liste de côté.

Quelqu'un connaît-il un moyen d'afficher même une barre de navigation qui permettrait à l'utilisateur de taper pour voir la liste des éléments sur la gauche? Alors que ce n'est pas seulement un écran avec les emoji?

Je détesterais laisser une note qui dit "Glissez vers la gauche pour voir la liste des fichiers/personnes/quoi que ce soit"

Je me souviens que UISplitViewController avait une propriété réduite qui pouvait être définie. Y a-t-il quelque chose comme ça ici?

19
MScottWaller

Dans Xcode 11 beta 3 , Apple a ajouté .navigationViewStyle(style:) à NavigationView .

enter image description here

Mis à jour pour Xcode 11 Beta 5 .
créer MasterView () & 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)
    }
}

dans mon ContentView :

var body: some View {

        NavigationView {

            MyMasterView()

            DetailsView()

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

Sortie:

enter image description here

10
Ketan Odedra

Pour l'instant, dans Xcode 11.2.1, cela n'a toujours rien changé. J'ai eu le même problème avec SplitView sur iPad et je l'ai résolu en ajoutant un rembourrage comme dans la réponse de Ketan Odedra, mais en le modifiant un peu:

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
}

Cela fonctionne parfaitement dans le simulateur. Mais lorsque j'ai soumis mon application pour examen, elle a été rejetée. Ce petit hack ne fonctionne pas sur le dispositif de relecture . Je n'ai pas de vrai iPad, donc je ne sais pas ce qui a causé ça. Essayez-le, cela fonctionnera peut-être pour vous.

Bien que cela ne fonctionne pas pour moi, j'ai demandé l'aide de Apple DTS. Ils me répondent que pour l'instant, l'API SwiftUI ne peut pas simuler complètement le comportement de SplitViewController d'UIKit. Mais il y a un Vous pouvez créer un SplitView personnalisé dans SwiftUI:

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

Et puis utilisez-le:

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

Sur un iPad, cela fonctionne. Mais il y a un problème (peut-être plus ..). Cette approche ruine la navigation sur iPhone car MasterView et DetailView ont leur NavigationView.

MISE À JOUR: Enfin, dans Xcode 11.4 beta 2, ils ont ajouté un bouton dans la barre de navigation qui indique la vue principale cachée. ????

4
Stanislav K.

Tests minimaux dans le simulateur, mais cela devrait être proche d'une vraie solution. L'idée est d'utiliser un EnvironmentObject pour contenir un var publié sur l'opportunité d'utiliser une double colonne NavigationStyle, ou une seule, puis de recréer le NavigationView si ce var changements.

L'environnementObjet:

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

Dans le Scene Delegate, définissez la variable au lancement, puis observez les rotations de l'appareil et modifiez-la éventuellement (le "1000" n'est pas la valeur correcte, point de départ):

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

Dans la vue de contenu de niveau supérieur, où le NavigationView est créé, utilisez un modificateur personnalisé au lieu d'utiliser directement un 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())
                }
            }
        }
    }
}

Comme mentionné précédemment, juste des tests sur simulateur, mais j'ai essayé de lancer dans les deux orientations, en tournant avec l'affichage maître, en tournant avec l'affichage détaillé, tout me semble bien.

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