swiftui 打开新页面方式(SwiftWebUI详细介绍示例图解)
swiftui 打开新页面方式(SwiftWebUI详细介绍示例图解).padding(.all) Text(" #\(counter)") func countUp() { counter = 1 } var body: some View { VStack {
swiftWebUI那么SwiftWebUI到底是 什么?它允许您编写 在Web浏览器中显示的SwiftUI 视图:
import SwiftWebUI
struct MainPage: View {
@State var counter = 0
func countUp() { counter = 1 }
var body: some View {
VStack {
Text(" #\(counter)")
.padding(.all)
.background(.green cornerRadius: 12)
.foregroundColor(.white)
.tapAction(self.countUp)
}
}
}
结果是:
与其他一些努力不同,这不仅仅是将SwiftUI Views渲染为HTML。它还在浏览器和Swift服务器中托管的代码之间建立了一个连接,允许交互 - 按钮,选择器,步进器,列表,导航,你得到它!
换句话说: SwiftWebUI 是浏览器的SwiftUI API的(很多但不是所有部分)的实现。
重复 免责声明:这是一个玩具项目!不要用于生产。使用它来了解有关SwiftUI及其内部工作的更多信息。
学习一次,随处使用SwiftUI的既定目标不是“ 一次编写,随处运行 ”,而是“ 一次学习,随处使用 ”。不要期望能够为iOS购买漂亮的SwiftUI应用程序,将代码放入SwiftWebUI项目并使其在浏览器中呈现完全相同。这不是重点。
关键是能够重用 knoff-hoff 并在不同平台之间共享它。在这种情况下,网络✔️
但是让我们深入了解细节并编写一个简单的SwiftWebUI应用程序。本着“一次学习,随处使用”的精神,首先观看这两个WWDC会议: 介绍SwiftUI 和 SwiftUI Essentials。我们在这个博客条目中并没有那么深入,但也建议使用这个(并且SwiftWebUI中主要支持这些概念): 通过SwiftUI的数据流。
要求
运行SwiftWebUI有三个选项:
macOS Catalina
可以使用 macOS Catalina 安装来运行SwiftWebUI。确保Catalina版本与您的Xcode 11测试版匹配!(“Swift ABI”♀️)
幸运的是,将Catalina安装在单独的APFS卷上非常容易 。并且需要安装 Xcode 11 才能获得SwiftUI大量使用的新Swift 5.1功能。了解?很好!
为什么需要Catalina?SwiftUI使用新的Swift 5.1运行时功能(例如不透明的结果类型)。这些功能在Mojave附带的Swift 5运行时中不可用。(另一个原因是使用仅在Catalina中可用的Combine,尽管可以使用OpenCombine修复该部分 )
tuxOS
SwiftWebUI现在使用OpenCombine在Linux上运行(也可以在没有它的情况下运行 ,但是有些东西不起作用,例如NavigationView)。
需要Swift 5.1快照。我们还提供了一个包含5.1快照的Docker镜像: helje5 / swift。
莫哈韦
Xcode 11beta iOS 13模拟器可以在Mojave上运行。您可以在iOS应用程序中运行SwiftWebUI。
第一个应用程序入门创建SwiftWebUI项目
启动Xcode 11,选择“File> New> Project ...”或按Cmd-Shift-N:
选择“macOS /命令行工具”项目模板:
给它一些好名字,让我们选择“AvocadoToast”:
然后我们将SwiftWebUI添加 为Swift Package Manager依赖项。该选项隐藏在“File / Swift Packages”菜单组中:
输入https://GitHub.com/SwiftWebUI/SwiftWebUI.git包URL:
使用“分支” master选项始终获取最新和最好的(您也可以使用修订版或develop分支):
最后将SwiftWebUI库添加到工具目标:
而已。你现在有一个可以的工具项目import SwiftWebUI。(Xcode可能需要一些时间来获取和构建依赖项。)
SwiftWebUI Hello World
让我们开始使用SwiftWebUI。打开main.swift文件并将其内容替换为:
import SwiftWebUI
SwiftWebUI.serve(Text("Holy Cow!"))
在Xcode中编译并运行应用程序,打开Safari并点击 http://localhost:1337/:
这里发生了什么:首先导入SwiftWebUI模块(不要意外导入macOSSwiftUI)
然后我们调用SwiftWebUI.serve哪个接受一个闭包返回一个视图,或者只是一个直视图 - 如下所示:一个 Text 视图(又名“UILabel”,它可以显示普通或格式化的文本)。
在幕后
在内部,该 serve 函数创建一个非常简单的 SwiftNIO HTTP服务器,侦听端口1337.当浏览器访问该服务器时,它会创建一个 会话 并将我们的(文本)视图传递给该会话。
最后,从View中,SwiftWebUI在服务器上创建一个“Shadow DOM”,将其呈现为HTML并将结果发送到浏览器。“Shadow DOM”(和状态对象保持在一起)存储在会话中。
这是SwiftWebUI应用程序与watchOS或iOS SwiftUI应用程序之间的区别。单个SwiftWebUI应用程序为一组用户提供服务,而不仅仅是一个用户。
添加一些交互
作为第一步,让我们更好地组织代码。在项目中创建一个新的Swift文件并调用它MainPage.swift。并为其添加一个简单的SwiftUI View定义:
import SwiftWebUI
struct MainPage: View {
var body: some View {
Text("Holy Cow!")
}
}
调整main.swift以提供我们的自定义视图:
SwiftWebUI.serve(MainPage())
我们现在可以main.swift独自完成所有工作并完成我们的习惯 View。让我们添加一些互动:
struct MainPage: View {
@State var counter = 3
func countUp() { counter = 1 }
var body: some View {
Text("Count is: \(counter)")
.tapAction(self.countUp)
}
}
我们 View 得到了一个持久 State 变量命名counter (不确定这是什么?再看一下SwiftUI的介绍)。还有一个小功能来撞击计数器。
然后我们使用SwiftUI tapAction 修饰符将事件处理程序附加到我们的Text。最后,我们在标签中显示当前值:
♀️ MAGIC
在幕后
这是如何运作的?当浏览器点击我们的端点时,SwiftWebUI在其中创建了会话和我们的“Shadow DOM”。然后它将描述我们View的HTML发送到浏览器。将tapAction通过增加工程onclick处理程序的HTML。SwiftWebUI还向浏览器发送(少量,没有大框架!)JavaScript,处理点击并将其转发到我们的Swift服务器。
然后通常的SwiftUI魔法开始.SwiftWebUI将click事件与我们的“Shadow DOM”中的事件处理程序相关联并调用该countUp 函数。通过修改counter State 变量,该函数使View的呈现无效。SwiftWebUI启动并扩展“Shadow DOM”中的更改。然后将这些更改发送回浏览器。
“更改”作为JSON数组发送,我们在页面中的小JavaScript可以处理这些数组。如果整个子树发生了变化(例如,如果用户导航到一个全新的视图),则更改可以是应用于innerHTML或的更大的HTML片段outerHTML。但通常情况下,这些变化都是小事,比如add class( set HTML attribute和浏览器DOM修改)。
优秀,基础工作。让我们带来更多的互动性。以下是基于用于在SwiftUI Essentials 演讲中演示SwiftUI的“Avocado Toast App” 。
HTML / CSS样式不是很完美也不漂亮。您可以想象我们不是网页设计师,可以在这里使用一些帮助。PR欢迎!
想要跳过细节,观看应用程序的GIF并在GitHub上下载:。
订单
谈话开始于此(~6:00),我们可以将其添加到新OrderForm.swift 文件中:
struct Order {
var includeSalt = false
var includeRedPepperFlakes = false
var quantity = 0
}
struct OrderForm: View {
@State private var order = Order()
func submitOrder() {}
var body: some View {
VStack {
Text("Avocado Toast").font(.title)
Toggle(isOn: $order.includeSalt) {
Text("Include Salt")
}
Toggle(isOn: $order.includeRedPepperFlakes) {
Text("Include Red Pepper Flakes")
}
Stepper(value: $order.quantity in: 1...10) {
Text("Quantity: \(order.quantity)")
}
Button(action: submitOrder) {
Text("Order")
}
}
}
}
对于直接测试SwiftWebUI.serve()中main.swift的新 OrderForm景观。
这就是它在浏览器中的样子:
SemanticUI用于在SwiftWebUI中设置一些样式。操作并不是严格要求的,它只是完成了一些看似体面的小部件。注意:仅使用CSS /字体,而不是JavaScript组件。
间歇:一些SwiftUI布局
在SwiftUI Essentials中大约16:00, 他们将获得SwiftUI布局和View修改器排序:
var body: some View {
HStack {
Text("")
.background(.green cornerRadius: 12)
.padding(.all)
Text(" => ")
Text("")
.padding(.all)
.background(.green cornerRadius: 12)
}
}
结果如下,请注意修饰符的排序是如何相关的:
SwiftWebUI尝试复制常见的SwiftUI布局,但还没有完全成功。毕竟它必须处理浏览器提供的布局系统。求助,Flexbox专家欢迎!
订单历史
回到应用程序,谈话(~19:50)介绍了 列表 视图,用于显示鳄梨土司订单历史记录。这就是它在Web上的外观:
该List视图散步完成的订单阵列之上,并为每一个(创建一个子视图OrderCell),通过在当前项目在列表中。
这是我们使用的代码:
struct OrderHistory: View {
let previousOrders : [ CompletedOrder ]
var body: some View {
List(previousOrders) { order in
OrderCell(order: order)
}
}
}
struct OrderCell: View {
let order : CompletedOrder
var body: some View {
HStack {
VStack(alignment: .leading) {
Text(order.summary)
Text(order.purchaseDate)
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
if order.includeSalt {
SaltIcon()
}
else {}
if order.includeRedPepperFlakes {
RedPepperFlakesIcon()
}
else {}
}
}
}
struct SaltIcon: View {
let body = Text("")
}
struct RedPepperFlakesIcon: View {
let body = Text("")
}
// Model
struct CompletedOrder: Identifiable {
var id : Int
var summary : String
var purchaseDate : String
var includeSalt = false
var includeRedPepperFlakes = false
}
SwiftWebUI列表视图效率非常低,它总是渲染整个子集。没有单元重用,没有任何东西在Web应用程序中有多种方法可以处理,例如通过使用分页或更多客户端逻辑。
所以你不必从谈话中输入样本数据,我们为你做了这样的事情:
let previousOrders : [ CompletedOrder ] = [
.init(id: 1 summary: "Rye with Almond Butter" purchaseDate: "2019-05-30")
.init(id: 2 summary: "Multi-Grain with Hummus" purchaseDate: "2019-06-02"
includeRedPepperFlakes: true)
.init(id: 3 summary: "Sourdough with Chutney" purchaseDate: "2019-06-08"
includeSalt: true includeRedPepperFlakes: true)
.init(id: 4 summary: "Rye with Peanut Butter" purchaseDate: "2019-06-09")
.init(id: 5 summary: "Wheat with Tapenade" purchaseDate: "2019-06-12")
.init(id: 6 summary: "Sourdough with Vegemite" purchaseDate: "2019-06-14"
includeSalt: true)
.init(id: 7 summary: "Wheat with Féroce" purchaseDate: "2019-06-31")
.init(id: 8 summary: "Rhy with Honey" purchaseDate: "2019-07-03")
.init(id: 9 summary: "Multigrain Toast" purchaseDate: "2019-07-04"
includeSalt: true)
.init(id: 10 summary: "Sourdough with Chutney" purchaseDate: "2019-07-06")
]
点差选择器
Picker控件以及如何使用它与enum's演示~43:00。首先是各种吐司选项的枚举:
enum AvocadoStyle {
case sliced mashed
}
enum BreadType: CaseIterable Hashable Identifiable {
case wheat white rhy
var name: String { return "\(self)".capitalized }
}
enum Spread: CaseIterable Hashable Identifiable {
case none almondButter peanutButter honey
case almou tapenade hummus mayonnaise
case kyopolou adjvar pindjur
case vegemite chutney cannedCheese feroce
case kartoffelkase tartarSauce
var name: String {
return "\(self)".map { $0.isUppercase ? " \($0)" : "\($0)" }
.joined().capitalized
}
}
我们可以将它们添加到我们的Orderstruct:
struct Order {
var includeSalt = false
var includeRedPepperFlakes = false
var quantity = 0
var avocadoStyle = AvocadoStyle.sliced
var spread = Spread.none
var breadType = BreadType.wheat
}
然后使用不同的Picker类型显示它们。如何循环枚举值非常简洁:
Form {
Section(header: Text("Avocado Toast").font(.title)) {
Picker(selection: $order.breadType label: Text("Bread")) {
ForEach(BreadType.allCases) { breadType in
Text(breadType.name).tag(breadType)
}
}
.pickerStyle(.radioGroup)
Picker(selection: $order.avocadoStyle label: Text("Avocado")) {
Text("Sliced").tag(AvocadoStyle.sliced)
Text("Mashed").tag(AvocadoStyle.mashed)
}
.pickerStyle(.radioGroup)
Picker(selection: $order.spread label: Text("Spread")) {
ForEach(Spread.allCases) { spread in
Text(spread.name).tag(spread) // there is no .name?!
}
}
}
}
结果:
再次,这需要一些CSS的爱,使它看起来更好......
“完成”应用程序
不,我们与原版略有不同,也没有真正完成它。它看起来并不那么棒,但它毕竟是一个演示
完成的应用程序可在GitHub上获得: AvocadoToast。
HTML和SemanticUI
UIViewRepresentable SwiftWebUI中的 对等体正在发出原始HTML。
提供了两种变体,HTML输出为String-as,或者通过HTML转义内容:
struct MyHTMLView: View {
var body: some View {
VStack {
HTML("<blink>Blinken Lights</blink>")
HTML("42 > 1337" escape: true)
}
}
}
使用此原语,您基本上可以构建所需的任何HTML。
更高一级甚至内部使用 HTMLContainer。例如,这是我们Stepper控件的实现:
var body: some View {
HStack {
HTMLContainer(classes: [ "ui" "icon" "buttons" "small" ]) {
Button(self.decrement) {
HTMLContainer("i" classes: [ "minus" "icon" ] body: {EmptyView()})
}
Button(self.increment) {
HTMLContainer("i" classes: [ "plus" "icon" ] body: {EmptyView()})
}
}
label
}
}
该HTMLContainer如果类,风格或属性的变化是“反应性”,也就是说,它会散发出常规的DOM变化(而不是重新绘制整个事情)
SemanticUI
SwiftWebUI还带有一些 预先设置的SemanticUI控件:
VStack {
SUILabel(Image(systemName: "mail")) { Text("42") }
HStack {
SUILabel(Image(...)) { Text("Joe") } ...
}
HStack {
SUILabel(Image(...)) { Text("Joe") } ...
}
HStack {
SUILabel(Image(...) Color("blue")
detail: Text("Friend"))
{
Text("Veronika")
} ...
}
}
......呈现这样的:
请注意,SwiftWebUI还支持一些SFSymbols图像名称(via Image(systemName:))。这些都得到了SemanticUI 对Font Awesome的 支持。
还有SUISegment,SUIFlag和SUICard:
SUICards {
SUICard(Image.unsplash(size: UXSize(width: 200 height: 200)
"Zebra" "Animal")
Text("Some Zebra")
meta: Text("Roaming the world since 1976"))
{
Text("A striped animal.")
}
SUICard(Image.unsplash(size: UXSize(width: 200 height: 200)
"Cow" "Animal")
Text("Some Cow")
meta: Text("Milk it"))
{
Text("Holy cow!.")
}
}
...渲染那些:
添加此类视图非常简单,非常有趣。可以使用WOComponent的 SwiftUI Views 快速构建相当复杂和美观的布局。
Image.unsplash根据运行的Unsplash API构造图像查询http://source.unsplash.com。只需给它一些查询术语,您想要的大小和可选范围。
注意:有时,特定的Unsplash服务似乎有点慢且不可靠。
文章英文版出处:
http://www.alwaysrightinstitute.com/swiftwebui/