因此,当我在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有一个可以设置的折叠属性。这里有这样的东西吗?
答案 0 :(得分:4)
目前,在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上可以使用。但是有一个问题(也许更多..)。 这种方法破坏了iPhone上的导航,因为MasterView和DetailView都有其NavigationView。
答案 1 :(得分:2)
在Simulator中进行最少的测试,但这应该接近实际的解决方案。想法是使用EnvironmentObject
来保存已发布的var,以决定使用双列NavigationStyle
还是使用单列NavigationView
,如果该var更改,则重新创建 final class AppEnvironment: ObservableObject {
@Published var useSideBySide: Bool = false
}
。 / p>
环境对象:
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()
}
在“场景委托”中,在启动时设置变量,然后观察设备旋转并可能对其进行更改(“ 1000”不是正确的值,起点):
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())
}
}
}
}
}
:
{{1}}
如前所述,仅是模拟器测试,但我尝试了两种方向的启动,即以“ Master”(主)显示,“ Detail”(细节)显示进行旋转。
答案 2 :(得分:0)
在 Xcode 11 beta 3 中,Apple已将.navigationViewStyle(style:)
添加到NavigationView
。
所以,我尝试了 Xcode 11 Beta 4 。
,然后创建 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)
}
}
在我的 ContentView 内:
var body: some View {
NavigationView {
MyMasterView()
DetailsView()
}.navigationViewStyle(.doubleColumn)
}
我碰到了iPad Pro(11英寸),但结果[仅在“纵向”下,在“横向”下工作]是相同的,没什么变化。
是错误吗? (嗯,我也不知道)。
因此,我更改了.navigationViewStyle(style:) property to
。default`
.navigationViewStyle(.doubleColumn)
相同的结果。
尝试使用最后一个属性.stack
结果不同。
它将强制navigationViewStyle
到.stack
输出:
**之后再尝试一次:**
简单的“ .padding()”工作。
var body: some View {
NavigationView {
MyMasterView()
DetailsView()
}.navigationViewStyle(.doubleColumn)
.padding()
}
输出:
如果与.padding()
一起使用, navigationViewStyle
必须存在。
答案 3 :(得分:0)
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!)
答案 4 :(得分:0)
对于当前版本(iOS 13.0-13.3.x),您可以使用我的代码。 我使用UIViewUpdater来访问底层的UIView及其UIViewController来调整条形项目。
我认为解决这个问题的UIViewUpdater方法是最快捷,最可靠的方法,您可以使用它来访问和修改其他UIView,与UIViewController相关的UIKit机制。
ContentView.swift
import SwiftUI
struct ContentView : View {
var people = ["Angela", "Juan", "Yeji"]
var body: some View {
NavigationView {
List {
ForEach(people, id: \.self) { person in
NavigationLink(destination: DetailView()) { Text(person) }
}
}
InitialDetailView()
}
}
}
struct DetailView : View {
var body: some View {
Text("Hello!")
}
}
struct InitialDetailView : View {
var body: some View {
NavigationView {
Text("?")
.navigationBarTitle("", displayMode: .inline) // .inline is neccesary for showing the left button item
.updateUIViewController {
$0.splitViewController?.preferredDisplayMode = .primaryOverlay // for showing overlay at initial
$0.splitViewController?.preferredDisplayMode = .automatic
}
.displayModeButtonItem()
}.navigationViewStyle(StackNavigationViewStyle())
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
解决方案的实用程序代码。将其放在该项目的任何Swift文件中。 Utility.swift
// View decoration
public extension View {
func updateUIView(_ action: @escaping (UIView) -> Void) -> some View {
background(UIViewUpdater(action: action).opacity(0))
}
func updateUIViewController(_ action: @escaping (UIViewController) -> Void) -> some View {
updateUIView {
guard let viewController = $0.viewController else { return }
action(viewController)
}
}
func displayModeButtonItem(_ position: NavigationBarPostion = .left) -> some View {
updateUIViewController { $0.setDisplayModeButtonItem(position) }
}
}
// UpdateUIView
struct UIViewUpdater : UIViewRepresentable {
let action: (UIView) -> Void
typealias UIViewType = InnerUIView
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIViewType {
UIViewType(action: action)
}
func updateUIView(_ uiView: UIViewType, context: UIViewRepresentableContext<Self>) {
// DispatchQueue.main.async { [action] in action(uiView) }
}
class InnerUIView : UIView {
let action: (UIView) -> Void
init(action: @escaping (UIView) -> Void) {
self.action = action
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMoveToWindow() {
super.didMoveToWindow()
update()
}
func update() {
action(self)
}
}
}
// UIView.viewController
public extension UIView {
var viewController: UIViewController? {
var i: UIResponder? = self
while i != nil {
if let vc = i as? UIViewController { return vc }
i = i?.next
}
return nil
}
}
// UIViewController.setDisplayModeButtonItem
public enum NavigationBarPostion {
case left
case right
}
public extension UIViewController {
func setDisplayModeButtonItem(_ position: NavigationBarPostion) {
guard let splitViewController = splitViewController else { return }
switch position {
case .left:
// keep safe to avoid replacing other left bar button item, e.g. navigation back
navigationItem.leftItemsSupplementBackButton = true
navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
case .right:
navigationItem.rightBarButtonItem = splitViewController.displayModeButtonItem
}
}
}