MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SwiftUI TabView与SplitView

2023-12-083.5k 阅读

SwiftUI TabView 基础

SwiftUI 的 TabView 为用户提供了一种在不同视图间快速切换的方式,常用于构建底部导航栏。这种设计模式在许多现代应用中广泛使用,比如社交媒体应用中的“首页”“消息”“个人中心”等切换。

创建简单的 TabView

首先,我们来看一个简单的 TabView 示例。假设我们有三个不同的视图,分别代表“首页”“设置”“关于”。

import SwiftUI

struct HomeView: View {
    var body: some View {
        Text("首页")
            .font(.title)
    }
}

struct SettingsView: View {
    var body: some View {
        Text("设置")
            .font(.title)
    }
}

struct AboutView: View {
    var body: some View {
        Text("关于")
            .font(.title)
    }
}

struct TabViewExample: View {
    var body: some View {
        TabView {
            HomeView()
               .tabItem {
                    Image(systemName: "house")
                    Text("首页")
                }
            SettingsView()
               .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
            AboutView()
               .tabItem {
                    Image(systemName: "info.circle")
                    Text("关于")
                }
        }
    }
}

struct TabViewExample_Previews: PreviewProvider {
    static var previews: some View {
        TabViewExample()
    }
}

在上述代码中,我们创建了三个简单的视图 HomeViewSettingsViewAboutView。然后在 TabViewExample 中,将这三个视图放入 TabView 中。每个视图通过 .tabItem 修饰符来定义其在底部导航栏中的显示内容,包括一个图标和文本。

动态切换 Tab

有时候,我们需要根据某些条件动态切换 TabView 中的选中项。可以通过绑定一个 @State 变量来实现。

import SwiftUI

struct DynamicTabView: View {
    @State private var selectedTab = 0

    var body: some View {
        TabView(selection: $selectedTab) {
            HomeView()
               .tag(0)
               .tabItem {
                    Image(systemName: "house")
                    Text("首页")
                }
            SettingsView()
               .tag(1)
               .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
            AboutView()
               .tag(2)
               .tabItem {
                    Image(systemName: "info.circle")
                    Text("关于")
                }
        }
        Button(action: {
            self.selectedTab = 1
        }) {
            Text("跳转到设置")
        }
    }
}

这里,我们使用 @State 变量 selectedTab 来跟踪当前选中的 TabTabViewselection 参数绑定到这个变量,每个视图通过 .tag 方法设置一个唯一标识。在视图中添加一个按钮,点击按钮可以将 selectedTab 设置为 1,从而跳转到“设置”页面。

TabView 的高级应用

自定义 Tab 样式

SwiftUI 允许我们自定义 TabView 的样式,使其更符合应用的整体风格。我们可以通过修改 UITabBarAppearance(在 iOS 上)或者 NSTabViewAppearance(在 macOS 上)来实现。

在 iOS 上,假设我们想要将 TabView 的背景颜色设置为自定义颜色,文字颜色也进行相应修改:

import SwiftUI

struct CustomTabView: View {
    var body: some View {
        TabView {
            HomeView()
               .tabItem {
                    Image(systemName: "house")
                    Text("首页")
                }
            SettingsView()
               .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
        }
       .onAppear {
            if let appearance = UITabBarAppearance() as? UITabBarAppearance {
                appearance.configureWithOpaqueBackground()
                appearance.backgroundColor = UIColor(Color.blue)
                appearance.stackedLayoutAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.white]
                appearance.stackedLayoutAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.yellow]
                UITabBar.appearance().standardAppearance = appearance
                UITabBar.appearance().scrollEdgeAppearance = appearance
            }
        }
    }
}

在这段代码中,我们使用 onAppear 修饰符在视图出现时进行样式设置。通过获取 UITabBarAppearance,我们配置了背景颜色、正常和选中状态下的文字颜色。

嵌套 TabView

在某些复杂的应用场景中,可能需要嵌套 TabView。例如,在一个主 TabView 的某个 Tab 中,又有一个子 TabView

import SwiftUI

struct MainTabView: View {
    var body: some View {
        TabView {
            HomeTabView()
               .tabItem {
                    Image(systemName: "house")
                    Text("首页")
                }
            SettingsView()
               .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
        }
    }
}

struct HomeTabView: View {
    var body: some View {
        TabView {
            SubHomeView1()
               .tabItem {
                    Image(systemName: "square")
                    Text("子页1")
                }
            SubHomeView2()
               .tabItem {
                    Image(systemName: "circle")
                    Text("子页2")
                }
        }
    }
}

struct SubHomeView1: View {
    var body: some View {
        Text("子页1内容")
    }
}

struct SubHomeView2: View {
    var body: some View {
        Text("子页2内容")
    }
}

在这个示例中,MainTabView 是主 TabView,其中“首页”对应的视图 HomeTabView 又是一个 TabView,包含两个子视图 SubHomeView1SubHomeView2

SwiftUI SplitView 基础

SplitView 是 SwiftUI 中用于在不同大小的屏幕上提供灵活布局的重要组件。它通常用于将屏幕空间分割为两个或多个部分,每个部分可以显示不同的视图。

创建简单的 SplitView

在 iOS 上,SplitView 常见于具有主 - 详细信息布局的应用中。下面是一个简单的 SplitView 示例:

import SwiftUI

struct MasterView: View {
    var body: some View {
        List(0..<10) { item in
            Text("Item \(item)")
        }
    }
}

struct DetailView: View {
    var item: Int
    var body: some View {
        Text("详细信息 for Item \(item)")
    }
}

struct SplitViewExample: View {
    @State private var selectedItem: Int?

    var body: some View {
        SplitView {
            MasterView()
               .onSelectItem { item in
                    self.selectedItem = item
                }
        } detail: {
            if let item = selectedItem {
                DetailView(item: item)
            } else {
                Text("请选择一个项目")
            }
        }
    }
}

extension MasterView {
    func onSelectItem(action: @escaping (Int) -> Void) -> some View {
        return self
           .onTapGesture {
                guard let index = self.index else { return }
                action(index)
            }
    }

    private var index: Int? {
        guard let indexPath = (self as? List)?.selection?.first else { return nil }
        return indexPath.item
    }
}

在这个示例中,MasterView 是一个包含列表的视图,DetailView 显示所选项目的详细信息。SplitViewExample 中使用 SplitView 将屏幕分为两部分,左边是 MasterView,右边根据 selectedItem 的值显示相应的 DetailView 或提示文本。

SplitView 的布局模式

SplitView 有几种不同的布局模式,主要通过 SplitViewStyle 来控制。

  1. SidebarSplitViewStyle:常用于在 iPad 上创建主 - 详细信息布局,左边是侧边栏,右边是详细内容。
struct SidebarSplitViewExample: View {
    @State private var selectedItem: Int?

    var body: some View {
        SplitView {
            MasterView()
               .onSelectItem { item in
                    self.selectedItem = item
                }
        } detail: {
            if let item = selectedItem {
                DetailView(item: item)
            } else {
                Text("请选择一个项目")
            }
        }
       .splitViewStyle(SidebarSplitViewStyle())
    }
}
  1. DoubleColumnSplitViewStyle:在横屏模式下,将屏幕分为两列,常见于 macOS 应用布局。
struct DoubleColumnSplitViewExample: View {
    @State private var selectedItem: Int?

    var body: some View {
        SplitView {
            MasterView()
               .onSelectItem { item in
                    self.selectedItem = item
                }
        } detail: {
            if let item = selectedItem {
                DetailView(item: item)
            } else {
                Text("请选择一个项目")
            }
        }
       .splitViewStyle(DoubleColumnSplitViewStyle())
    }
}

SplitView 的高级应用

响应式布局

SplitView 非常适合实现响应式布局,以适应不同的屏幕尺寸和方向。我们可以根据环境变量动态调整 SplitView 的布局。

struct ResponsiveSplitView: View {
    @State private var selectedItem: Int?

    var body: some View {
        if UIDevice.current.userInterfaceIdiom ==.pad && UIScreen.main.bounds.width > UIScreen.main.bounds.height {
            SplitView {
                MasterView()
                   .onSelectItem { item in
                        self.selectedItem = item
                    }
            } detail: {
                if let item = selectedItem {
                    DetailView(item: item)
                } else {
                    Text("请选择一个项目")
                }
            }
           .splitViewStyle(SidebarSplitViewStyle())
        } else {
            TabView {
                MasterView()
                   .tabItem {
                        Image(systemName: "list.bullet")
                        Text("列表")
                    }
                if let item = selectedItem {
                    DetailView(item: item)
                       .tabItem {
                            Image(systemName: "info.circle")
                            Text("详情")
                        }
                }
            }
        }
    }
}

在这段代码中,我们检查设备是否为 iPad 且处于横屏模式。如果是,使用 SidebarSplitViewStyleSplitView;否则,使用 TabView 来提供类似的功能,以适应不同的屏幕条件。

嵌套 SplitView

TabView 类似,SplitView 也可以进行嵌套,以实现更复杂的布局。

struct OuterSplitView: View {
    var body: some View {
        SplitView {
            Text("左侧视图")
        } detail: {
            InnerSplitView()
        }
       .splitViewStyle(SidebarSplitViewStyle())
    }
}

struct InnerSplitView: View {
    var body: some View {
        SplitView {
            Text("中间视图")
        } detail: {
            Text("右侧视图")
        }
       .splitViewStyle(DoubleColumnSplitViewStyle())
    }
}

在这个示例中,OuterSplitView 将屏幕分为左右两部分,右边部分又通过 InnerSplitView 进一步分为两部分,展示了嵌套 SplitView 的用法。

TabView 与 SplitView 的结合使用

在一些复杂的应用场景中,我们可能需要将 TabViewSplitView 结合起来使用。比如,在一个应用的底部有 TabView 作为主要导航,其中某个 Tab 内又使用 SplitView 进行更详细的布局。

struct CombinedView: View {
    @State private var selectedTab = 0
    @State private var selectedItem: Int?

    var body: some View {
        TabView(selection: $selectedTab) {
            HomeTab()
               .tag(0)
               .tabItem {
                    Image(systemName: "house")
                    Text("首页")
                }
            SettingsView()
               .tag(1)
               .tabItem {
                    Image(systemName: "gear")
                    Text("设置")
                }
        }
    }

    struct HomeTab: View {
        @State private var selectedItem: Int?

        var body: some View {
            SplitView {
                MasterView()
                   .onSelectItem { item in
                        self.selectedItem = item
                    }
            } detail: {
                if let item = selectedItem {
                    DetailView(item: item)
                } else {
                    Text("请选择一个项目")
                }
            }
           .splitViewStyle(SidebarSplitViewStyle())
        }
    }
}

在这个 CombinedView 中,我们有一个 TabView 作为主要导航。“首页”对应的 HomeTab 视图内部使用了 SplitView,这样就结合了两者的优势,为用户提供丰富且灵活的界面交互体验。

处理设备特定的行为

iOS 与 macOS 的差异

  1. 外观差异:iOS 和 macOS 的 TabViewSplitView 在外观上有明显差异。在 iOS 上,TabView 通常以底部导航栏的形式呈现,而在 macOS 上,TabView 更多以标签页的形式出现在窗口内。SplitView 在 iOS 上常用于主 - 详细信息布局,而在 macOS 上有更多不同的布局模式,如 DoubleColumnSplitViewStyle 等。

  2. 交互差异:在 iOS 上,用户通过触摸屏幕来切换 TabView 中的标签或与 SplitView 中的视图交互。而在 macOS 上,用户使用鼠标和键盘进行操作,例如通过点击标签切换 TabView,通过拖动分隔条调整 SplitView 各部分的大小。

适配不同设备

  1. 使用环境变量:我们可以利用 UIDevice.current.userInterfaceIdiom 来判断设备是 iPhone、iPad 还是 Mac。例如,在前面的 ResponsiveSplitView 示例中,我们根据设备类型和屏幕方向动态调整布局,使用 SplitViewTabView 以提供最佳用户体验。

  2. 预览不同设备:在 Xcode 中,我们可以使用模拟器和预览功能,快速查看应用在不同设备上的显示效果。通过选择不同的设备模拟器,如 iPhone、iPad 或 Mac,我们可以实时调整布局和样式,确保应用在各种设备上都能正常工作且具有良好的用户体验。

性能优化

TabView 性能优化

  1. 视图重用:当 TabView 中有大量视图时,确保视图进行了正确的重用。SwiftUI 会自动处理一些视图的重用,但对于复杂视图,我们可能需要手动优化。例如,在列表视图中使用 ForEach 时,确保为每个项目提供唯一的 id,这样 SwiftUI 可以更有效地管理视图的创建和销毁。

  2. 避免不必要的重绘:尽量减少 TabView 中视图的状态变化,避免不必要的重绘。如果某个视图的状态变化不会影响其显示,考虑将该状态提取到父视图中,或者使用 @StateObject 来管理视图状态,确保只有真正需要更新的视图才会被重绘。

SplitView 性能优化

  1. 懒加载:对于 SplitView 中可能不会立即显示的视图,采用懒加载的方式。例如,在主 - 详细信息布局中,如果详细视图在用户选择主视图项目之前不需要加载,可以使用 LazyView 来延迟加载详细视图,从而提高应用的启动性能和内存使用效率。
struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autocallable @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}
  1. 优化布局计算SplitView 的布局计算可能会比较复杂,尤其是在嵌套使用或动态调整大小时。尽量简化 SplitView 内部视图的布局结构,避免过度嵌套视图和复杂的布局约束,以减少布局计算的时间和资源消耗。

总结与最佳实践

  1. 遵循设计规范:无论是 TabView 还是 SplitView,都要遵循相应平台的设计规范。在 iOS 上,遵循苹果的人机界面指南,确保 TabView 的标签数量、图标样式和交互方式符合用户习惯;在 macOS 上,遵循 macOS 的设计原则,合理使用 SplitView 的布局模式。

  2. 提供清晰的导航TabViewSplitView 都是用于导航和信息展示的重要组件。确保用户能够轻松理解不同标签或视图之间的关系,通过清晰的图标、文本标签和合理的布局,引导用户快速找到所需信息。

  3. 测试与优化:在不同设备、不同屏幕尺寸和方向上进行充分测试,确保 TabViewSplitView 的功能和性能都能满足用户需求。及时发现并修复布局问题、交互异常和性能瓶颈,提供流畅的用户体验。

通过深入理解和合理运用 TabViewSplitView,开发者可以创建出功能丰富、用户体验良好的应用程序,满足不同用户在不同设备上的使用需求。无论是简单的底部导航还是复杂的主 - 详细信息布局,这两个组件都为 SwiftUI 应用开发提供了强大的工具。