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?
Dans Xcode 11 beta 3 , Apple a ajouté .navigationViewStyle(style:)
à NavigationView
.
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:
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.
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.
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!)