【译】Fucking SwiftUI
2020/2/20 23:06:16
本文主要是介绍【译】Fucking SwiftUI,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
欢迎访问我的博客原文地址。
译文:Fucking Swift UI - Cheat Sheet
译者的话:翻译过程中,发现了原文中的几个错误,我向作者@sarunw提出意见后,直接在译文中改掉了,如果您发现文中内容有误,欢迎与我联系。
关于 SwiftUI,您在下文中看到的所有答案并不是完整详细的,它只能充当一份备忘单,或是检索表。
常见问题
关于 SwiftUI 的常见问题:
是否需要学 SwiftUI?
是
是否有必要现在就学 SwiftUI?
看情况,因为 SwiftUI 目前只能在 iOS 13、macOS 10.15、tvOS 13和 watchOS 6 上运行。如果您要开发的新应用计划仅针对前面提到的 OS 系统,我会说是。 但是,如果您打算找工作或是无法确保会在此 OS 版本的客户端项目上工作,则可能要等一两年,再考虑迁移成 SwiftUI,毕竟大多数客户端工作都希望支持尽可能多的用户,这意味着您的应用必须兼容多个 OS 系统。 因此,一年后再去体验优雅的 SwiftUI 也许是最好的时机。
是否需要学 UIKit/AppKit/WatchKit?
是的,就长时间来看,UIKit 仍将是 iOS 架构的重要组成部分。现在的 SwiftUI 并不成熟完善,我认为即使您打算用 SwiftUI 来开发,仍然不时需要用到 UIKit。
SwiftUI 能代替 UIKit/AppKit/WatchKit 吗?
现在不行,但将来也许会。SwiftUI 虽然是刚刚推出的,它看起来已经很不错。我希望两者能长期共存,SwiftUI 还很年轻,它还需要几年的打磨成长才能去代替 UIKit/AppKit/WatchKit。
如果我现在只能学习一种,那么应该选择 UIKit/AppKit/WatchKit 还是 SwiftUI?
UIKit。 您始终可以依赖 UIKit,它用起来一直不错,且未来一段时间仍然可用。如果您直接从 SwiftUI 开始学习,可能会遗漏了解一些功能。
SwiftUI 的控制器在哪里?
没有了。 如今页面间直接通过响应式编程框架 Combine 交互。Combine 也作为新的通信方式替代了 UIViewController。
要求
- Xcode 11 Beta(从 Apple 官网下载)
- iOS 13 / macOS 10.15 / tvOS 13 / watchOS 6
- macOS Catalina,以便在画布上呈现 SwiftUI(从 Apple 官网下载)
想要体验 SwiftUI 画布,但不想在您的电脑上安装 macOS Catalina beta 系统 您可以与当前的 macOS 版本并行安装 Catalina。这里介绍了如何在单独的 APFS 卷上安装 macOS
SwiftUI 中等效的 UIKit
视图控制器
UIKit | SwiftUI | 备注 |
---|---|---|
UIViewController | View | - |
UITableViewController | List | - |
UICollectionViewController | - | 目前,还没有 SwiftUI 的替代品,但是您可以像Composing Complex Interfaces's tutorial里那样,使用 List 的组成来模拟布局 |
UISplitViewController | NavigationView | Beta 5中有部分支持,但仍然无法使用。 |
UINavigationController | NavigationView | - |
UIPageViewController | - | - |
UITabBarController | TabView | - |
UISearchController | - | - |
UIImagePickerController | - | - |
UIVideoEditorController | - | - |
UIActivityViewController | - | - |
UIAlertController | Alert | - |
视图和控件
UIKit | SwiftUI | 备注 |
---|---|---|
UILabel | Text | - |
UITabBar | TabView | - |
UITabBarItem | TabView | TabView 里的 .tabItem |
UITextField | TextField | Beta 5中有部分支持,但仍然无法使用。 |
UITableView | List | VStack 和 Form 也可以 |
UINavigationBar | NavigationView | NavigationView 的一部分 |
UIBarButtonItem | NavigationView | NavigationView 里的 .navigationBarItems |
UICollectionView | - | - |
UIStackView | HStack | .axis == .Horizontal |
UIStackView | VStack | .axis == .Vertical |
UIScrollView | ScrollView | - |
UIActivityIndicatorView | - | - |
UIImageView | Image | - |
UIPickerView | Picker | - |
UIButton | Button | - |
UIDatePicker | DatePicker | - |
UIPageControl | - | - |
UISegmentedControl | Picker | Picker 中的一种样式 SegmentedPickerStyle |
UISlider | Slider | - |
UIStepper | Stepper | - |
UISwitch | Toggle | - |
UIToolBar | - | - |
框架集成 - SwiftUI 中的 UIKit
将 SwiftUI 视图集成到现有应用程序中,并将 UIKit 视图和控制器嵌入 SwiftUI 视图层次结构中。
UIKit | SwiftUI | 备注 |
---|---|---|
UIView | UIViewRepresentable | - |
UIViewController | UIViewControllerRepresentable | - |
框架集成 - UIKit 中的 SwiftUI
将 SwiftUI 视图集成到现有应用程序中,并将 UIKit 视图和控制器嵌入 SwiftUI 视图层次结构中。
UIKit | SwiftUI | 备注 |
---|---|---|
UIView (UIHostingController) | View | 没有直接转换为 UIView 的方法,但是您可以使用容器视图将 UIViewController 中的视图添加到视图层次结构中 |
UIViewController (UIHostingController) | View | - |
SwiftUI - 视图和控件
Text
显示一行或多行只读文本的视图。
Text("Hello World") 复制代码
样式:
Text("Hello World") .bold() .italic() .underline() .lineLimit(2) 复制代码
Text
中填入的字符串也用作 LocalizedStringKey
,因此也会直接获得 NSLocalizedString
的特性。
Text("This text used as localized key") 复制代码
直接在文本视图里格式化文本。 实际上,这不是 SwiftUI 的功能,而是 Swift 5的字符串插入特性。
static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .long return formatter }() var now = Date() var body: some View { Text("What time is it?: \(now, formatter: Self.dateFormatter)") } 复制代码
可以直接用 +
拼接 Text
文本:
Text("Hello ") + Text("World!").bold() 复制代码
文字对齐方式:
Text("Hello\nWorld!").multilineTextAlignment(.center) 复制代码
TextField
显示可编辑文本界面的控件。
@State var name: String = "John" var body: some View { TextField("Name's placeholder", text: $name) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() } 复制代码
SecureField
用户安全地输入私人文本的控件。
@State var password: String = "1234" var body: some View { SecureField($password) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding() } 复制代码
Image
显示图像的视图。
Image("foo") //图像名字为 foo 复制代码
我们可以使用新的 SF Symbols:
Image(systemName: "clock.fill") 复制代码
您可以通过为系统图标添加样式,来匹配您使用的字体:
Image(systemName: "cloud.heavyrain.fill") .foregroundColor(.red) .font(.title) Image(systemName: "clock") .foregroundColor(.red) .font(Font.system(.largeTitle).bold()) 复制代码
为图片增加样式:
Image("foo") .resizable() // 调整大小,以便填充所有可用空间 .aspectRatio(contentMode: .fit) 复制代码
Button
在触发时执行操作的控件。
Button( action: { // 点击事件 }, label: { Text("Click Me") } ) 复制代码
如果按钮的标签只有 Text
,则可以通过下面这种简单的方式进行初始化:
Button("Click Me") { // 点击事件 } 复制代码
您可以像这样给按钮添加属性:
Button(action: { }, label: { Image(systemName: "clock") Text("Click Me") Text("Subtitle") }) .foregroundColor(Color.white) .padding() .background(Color.blue) .cornerRadius(5) 复制代码
NavigationLink
按下时会触发导航演示的按钮。它用作代替 pushViewController
。
NavigationView { NavigationLink(destination: Text("Detail") .navigationBarTitle(Text("Detail")) ) { Text("Push") }.navigationBarTitle(Text("Master")) } 复制代码
为了增强可读性,可以把 destination
包装成自定义视图 DetailView
的方式:
NavigationView { NavigationLink(destination: DetailView()) { Text("Push") }.navigationBarTitle(Text("Master")) } 复制代码
但不确定是 Bug 还是设计使然,上述代码 在 Beta 5 中的无法正常执行。尝试像这样把
NavigationLink
包装进列表中试一下:
NavigationView { List { NavigationLink(destination: Text("Detail")) { Text("Push") }.navigationBarTitle(Text("Master")) } } 复制代码
如果 NavigationLink
的标签只有 Text
,则可以用这样更简单的方式初始化:
NavigationLink("Detail", destination: Text("Detail").navigationBarTitle(Text("Detail"))) 复制代码
Toggle
在开/关状态之间切换的控件。
@State var isShowing = true // toggle 状态值 Toggle(isOn: $isShowing) { Text("Hello World") } 复制代码
如果 Toggle
的标签只有 Text
,则可以用这样更简单的方式初始化:
Toggle("Hello World", isOn: $isShowing) 复制代码
Picker
从一组互斥值中进行选择的控件。
选择器样式根据其被父视图进行更改,在表单或列表下作为一个列表行显示,点击可以推出新界面展示所有的选项卡。
NavigationView { Form { Section { Picker(selection: $selection, label: Text("Picker Name") , content: { Text("Value 1").tag(0) Text("Value 2").tag(1) Text("Value 3").tag(2) Text("Value 4").tag(3) }) } } } 复制代码
您可以使用 .pickerStyle(WheelPickerStyle())
覆盖样式。
在 iOS 13 中, UISegmentedControl
也只是 Picker
的一种样式。
@State var mapChoioce = 0 var settings = ["Map", "Transit", "Satellite"] Picker("Options", selection: $mapChoioce) { ForEach(0 ..< settings.count) { index in Text(self.settings[index]) .tag(index) } }.pickerStyle(SegmentedPickerStyle()) 复制代码
分段控制器在iOS 13中也焕然一新了。
DatePicker
选择日期的控件。
日期选择器样式也会根据其父视图进行更改,在表单或列表下作为一个列表行显示,点击可以扩展到日期选择器(就像日历 App 一样)。
@State var selectedDate = Date() var dateClosedRange: ClosedRange<Date> { let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())! let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())! return min...max } NavigationView { Form { Section { DatePicker( selection: $selectedDate, in: dateClosedRange, displayedComponents: .date, label: { Text("Due Date") } ) } } } 复制代码
不在表单或列表里,它就可以作为普通的旋转选择器。
@State var selectedDate = Date() var dateClosedRange: ClosedRange<Date> { let min = Calendar.current.date(byAdding: .day, value: -1, to: Date())! let max = Calendar.current.date(byAdding: .day, value: 1, to: Date())! return min...max } DatePicker( selection: $selectedDate, in: dateClosedRange, displayedComponents: [.hourAndMinute, .date], label: { Text("Due Date") } ) 复制代码
如果 DatePicker
的标签只有 Text
,则可以用这样更简单的方式初始化:
DatePicker("Due Date", selection: $selectedDate, in: dateClosedRange, displayedComponents: [.hourAndMinute, .date]) 复制代码
可以使用 ClosedRange
、PartialRangeThrough
和 PartialRangeFrom
来设置 minimumDate
和 maximumDate
。
DatePicker("Minimum Date", selection: $selectedDate, in: Date()..., displayedComponents: [.date]) DatePicker("Maximum Date", selection: $selectedDate, in: ...Date(), displayedComponents: [.date]) 复制代码
Slider
从有界的线性范围中选择一个值的控件。
@State var progress: Float = 0 Slider(value: $progress, from: 0.0, through: 100.0, by: 5.0) 复制代码
Slider 虽然没有 minimumValueImage
和 maximumValueImage
属性, 但可以借助 HStack
实现。
@State var progress: Float = 0 HStack { Image(systemName: "sun.min") Slider(value: $progress, from: 0.0, through: 100.0, by: 5.0) Image(systemName: "sun.max.fill") }.padding() 复制代码
Stepper
用于执行语义上递增和递减动作的控件。
@State var quantity: Int = 0 Stepper(value: $quantity, in: 0...10, label: { Text("Quantity \(quantity)")}) 复制代码
如果您的 Stepper
的标签只有 Text
,则可以用这样更简单的方式初始化:
Stepper("Quantity \(quantity)", value: $quantity, in: 0...10) 复制代码
如果您要一个自己管理的数据源的控件,可以这样写:
@State var quantity: Int = 0 Stepper(onIncrement: { self.quantity += 1 }, onDecrement: { self.quantity -= 1 }, label: { Text("Quantity \(quantity)") }) 复制代码
SwiftUI - 页面布局与演示
HStack
水平排列子元素的视图。
创建一个水平排列的静态列表:
HStack (alignment: .center, spacing: 20){ Text("Hello") Divider() Text("World") } 复制代码
VStack
垂直排列子元素的视图。
创建一个垂直排列的静态列表:
VStack (alignment: .center, spacing: 20){ Text("Hello") Divider() Text("World") } 复制代码
ZStack
子元素会在 z轴方向上叠加,同时在垂直/水平轴上对齐的视图。
ZStack { Text("Hello") .padding(10) .background(Color.red) .opacity(0.8) Text("World") .padding(20) .background(Color.red) .offset(x: 0, y: 40) } 复制代码
List
用于显示排列一系列数据行的容器。
创建一个静态可滚动列表:
List { Text("Hello world") Text("Hello world") Text("Hello world") } 复制代码
表单里的内容可以混搭:
List { Text("Hello world") Image(systemName: "clock") } 复制代码
创建一个动态列表:
let names = ["John", "Apple", "Seed"] List(names) { name in Text(name) } 复制代码
加入分区:
List { Section(header: Text("UIKit"), footer: Text("We will miss you")) { Text("UITableView") } Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) { Text("List") } } 复制代码
要使其成为分组列表,请添加 .listStyle(GroupedListStyle())
:
List { Section(header: Text("UIKit"), footer: Text("We will miss you")) { Text("UITableView") } Section(header: Text("SwiftUI"), footer: Text("A lot to learn")) { Text("List") } }.listStyle(GroupedListStyle()) 复制代码
ScrollView
滚动视图。
ScrollView(alwaysBounceVertical: true) { Image("foo") Text("Hello World") } 复制代码
Form
对数据输入的控件进行分组的容器,例如在设置或检查器中。
您可以往表单中插入任何内容,它将为表单渲染适当的样式。
NavigationView { Form { Section { Text("Plain Text") Stepper(value: $quantity, in: 0...10, label: { Text("Quantity") }) } Section { DatePicker($date, label: { Text("Due Date") }) Picker(selection: $selection, label: Text("Picker Name") , content: { Text("Value 1").tag(0) Text("Value 2").tag(1) Text("Value 3").tag(2) Text("Value 4").tag(3) }) } } } 复制代码
Spacer
一块既能在包含栈布局时沿主轴伸展,也能在不包含栈时沿两个轴展开的灵活空间。
HStack { Image(systemName: "clock") Spacer() Text("Time") } 复制代码
Divider
用于分隔其它内容的可视化元素。
HStack { Image(systemName: "clock") Divider() Text("Time") }.fixedSize() 复制代码
NavigationView
用于渲染视图堆栈的视图,这些视图会展示导航层次结构中的可见路径。
NavigationView { List { Text("Hello World") } .navigationBarTitle(Text("Navigation Title")) // 默认使用大标题样式 } 复制代码
对于旧样式标题:
NavigationView { List { Text("Hello World") } .navigationBarTitle(Text("Navigation Title"), displayMode: .inline) } 复制代码
增加 UIBarButtonItem
NavigationView { List { Text("Hello World") } .navigationBarItems(trailing: Button(action: { // Add action }, label: { Text("Add") }) ) .navigationBarTitle(Text("Navigation Title")) } 复制代码
用 NavigationLink 添加 show
/push
功能。
作为 UISplitViewController
:
NavigationView { List { NavigationLink("Go to detail", destination: Text("New Detail")) }.navigationBarTitle("Master") Text("Placeholder for Detail") } 复制代码
您可以使用两种新的样式属性:stack
和 doubleColumn
为 NavigationView 设置样式。默认情况下,iPhone 和 Apple TV 上的导航栏上显示导航堆栈,而在 iPad 和 Mac 上,显示的是拆分样式的导航视图。
您可以通过 .navigationViewStyle
重写样式:
NavigationView { MyMasterView() MyDetailView() } .navigationViewStyle(StackNavigationViewStyle()) 复制代码
在 beta 3中,NavigationView
支持拆分视图,但它仅支持非常基本的结构,其中主视图为列表,详细视图为叶视图,我期待在下一个 release 版本中能有优化补充。
TabView
使用交互式用户界面元素在多个子视图之间切换的视图。
TabView { Text("First View") .font(.title) .tabItem({ Text("First") }) .tag(0) Text("Second View") .font(.title) .tabItem({ Text("Second") }) .tag(1) } 复制代码
标签元素支持同时显示图像和文本, 您也可以使用 SF Symbols。
TabView { Text("First View") .font(.title) .tabItem({ Image(systemName: "circle") Text("First") }) .tag(0) Text("Second View") .font(.title) .tabItem(VStack { Image("second") Text("Second") }) .tag(1) } 复制代码
您也可以省略 VStack
:
TabView { Text("First View") .font(.title) .tabItem({ Image(systemName: "circle") Text("First") }) .tag(0) Text("Second View") .font(.title) .tabItem({ Image("second") Text("Second") }) .tag(1) } 复制代码
Alert
一个展示警告信息的容器。
我们可以根据布尔值显示 Alert
。
@State var isError: Bool = false Button("Alert") { self.isError = true }.alert(isPresented: $isError, content: { Alert(title: Text("Error"), message: Text("Error Reason"), dismissButton: .default(Text("OK"))) }) 复制代码
它也可与 Identifiable
项目绑定。
@State var error: AlertError? var body: some View { Button("Alert Error") { self.error = AlertError(reason: "Reason") }.alert(item: $error, content: { error in alert(reason: error.reason) }) } func alert(reason: String) -> Alert { Alert(title: Text("Error"), message: Text(reason), dismissButton: .default(Text("OK")) ) } struct AlertError: Identifiable { var id: String { return reason } let reason: String } 复制代码
Modal
模态视图的存储类型。
我们可以根据布尔值显示 Modal
。
@State var isModal: Bool = false var modal: some View { Text("Modal") } Button("Modal") { self.isModal = true }.sheet(isPresented: $isModal, content: { self.modal }) 复制代码
它也可与 Identifiable
项目绑定。
@State var detail: ModalDetail? var body: some View { Button("Modal") { self.detail = ModalDetail(body: "Detail") }.sheet(item: $detail, content: { detail in self.modal(detail: detail.body) }) } func modal(detail: String) -> some View { Text(detail) } struct ModalDetail: Identifiable { var id: String { return body } let body: String } 复制代码
ActionSheet
操作表视图的存储类型。
我们可以根据布尔值显示 ActionSheet
。
@State var isSheet: Bool = false var actionSheet: ActionSheet { ActionSheet(title: Text("Action"), message: Text("Description"), buttons: [ .default(Text("OK"), action: { }), .destructive(Text("Delete"), action: { }) ] ) } Button("Action Sheet") { self.isSheet = true }.actionSheet(isPresented: $isSheet, content: { self.actionSheet }) 复制代码
它也可与 Identifiable
项目绑定。
@State var sheetDetail: SheetDetail? var body: some View { Button("Action Sheet") { self.sheetDetail = ModSheetDetail(body: "Detail") }.actionSheet(item: $sheetDetail, content: { detail in self.sheet(detail: detail.body) }) } func sheet(detail: String) -> ActionSheet { ActionSheet(title: Text("Action"), message: Text(detail), buttons: [ .default(Text("OK"), action: { }), .destructive(Text("Delete"), action: { }) ] ) } struct SheetDetail: Identifiable { var id: String { return body } let body: String } 复制代码
框架集成 - SwiftUI 中的 UIKit
UIViewRepresentable
表示 UIKit 视图的视图,当您想在 SwiftUI 中使用 UIView 时,请使用它。
要使任何 UIView 在 SwiftUI 中可用,请创建一个符合 UIViewRepresentable 的包装器视图。
import UIKit import SwiftUI struct ActivityIndicator: UIViewRepresentable { @Binding var isAnimating: Bool func makeUIView(context: Context) -> UIActivityIndicatorView { let v = UIActivityIndicatorView() return v } func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) { if isAnimating { uiView.startAnimating() } else { uiView.stopAnimating() } } } 复制代码
如果您想要桥接 UIKit 里的数据绑定 (delegate, target/action) 就使用 Coordinator
, 具体见 SwiftUI 教程。
import SwiftUI import UIKit struct PageControl: UIViewRepresentable { var numberOfPages: Int @Binding var currentPage: Int func makeUIView(context: Context) -> UIPageControl { let control = UIPageControl() control.numberOfPages = numberOfPages control.addTarget( context.coordinator, action: #selector(Coordinator.updateCurrentPage(sender:)), for: .valueChanged) return control } func updateUIView(_ uiView: UIPageControl, context: Context) { uiView.currentPage = currentPage } func makeCoordinator() -> Coordinator { Coordinator(self) } // This is where old paradigm located class Coordinator: NSObject { var control: PageControl init(_ control: PageControl) { self.control = control } @objc func updateCurrentPage(sender: UIPageControl) { control.currentPage = sender.currentPage } } } 复制代码
UIViewControllerRepresentable
表示 UIKit 视图控制器的视图。当您想在 SwiftUI 中使用 UIViewController 时,请使用它。
要使任何 UIViewController 在 SwiftUI 中可用,请创建一个符合 UIViewControllerRepresentable 的包装器视图,具体见 SwiftUI 教程。
import SwiftUI import UIKit struct PageViewController: UIViewControllerRepresentable { var controllers: [UIViewController] func makeUIViewController(context: Context) -> UIPageViewController { let pageViewController = UIPageViewController( transitionStyle: .scroll, navigationOrientation: .horizontal) return pageViewController } func updateUIViewController(_ pageViewController: UIPageViewController, context: Context) { pageViewController.setViewControllers( [controllers[0]], direction: .forward, animated: true) } } 复制代码
框架集成 - UIKit 中的 SwiftUI
UIHostingController
表示 SwiftUI 视图的 UIViewController。
let vc = UIHostingController(rootView: Text("Hello World")) let vc = UIHostingController(rootView: ContentView()) 复制代码
来源
这篇关于【译】Fucking SwiftUI的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值