
我将首先观察到,本文中讨论的应用程序如果要使用
Live Previews
需要
Xcode 11和
MacOS Catalina,如果要使用模拟器则需要
Mojave
。 应用程序代码在
Github上 。
Apple
今年在
WWDC 2019上宣布了
SwiftUI
,这是一种在所有
Apple
设备上构建用户界面(UI)的新声明方式。 这几乎与通常的
UIKit
完全不同,而且我-和许多其他开发人员一样-确实希望看到这个新工具的实际应用。
本文介绍了使用
SwiftUI
解决问题的经验,该问题的
UIKit
的代码无比复杂,在我看来无法以可读的方式阅读。
该任务与上一次针对
Android
,
iOS
和
JS
开发人员的
Telegram竞赛有关,该竞赛于2019年3月10日至3月24日举行。 在这场比赛中,提出了一个简单的任务,即根据时间基于
JSON
数据以图形方式显示Internet上某种资源的使用强度。 作为
iOS
开发人员,您应该使用
Swift
将从头编写的代码提交给竞争对手,而无需使用任何无关的专业库进行绘图。
此任务需要具备使用iOS图形和动画功能的技能:
核心图形 ,
核心动画 ,
金属 ,
OpenGL ES 。 其中一些工具是底层的,非面向对象的编程工具。 本质上,在
iOS
中,没有乍看之下可用于解决图形任务的模板。 因此,每个参赛者都根据
Metal ,
CALayers ,
OpenGL ,
CADisplayLink发明了自己的动画器(
Render )。 这产生了大量的代码,无法借用和开发任何东西,因为这些都是纯粹的“受版权保护”的作品,只有作者才能真正开发。 但是,事实并非如此。
在6月初的
WWDC 2019上 ,
SwifUI
出现了-由
Apple
开发,用
Swift
编写的新
framework
,旨在用代码声明性地描述用户界面(
UI
)。 您可以确定哪些
subviews
显示在
View
,哪些数据会导致这些
subviews
发生变化,需要对它们应用哪些修饰符,以将它们放置在正确的位置,并具有正确的大小和样式。
SwiftUI
一个同样重要的元素是对用户可修改数据流的控制,这反过来又更新了
UI
。
在本文中,我想展示如何快速轻松地解决
SwiftUI
上的
Telegram竞赛的任务。 另外,这是一个非常令人兴奋的过程。
工作任务
竞争性应用程序应该使用
Telegram
提供的数据在屏幕上同时显示5个“图表集”。 对于一组“图表”,
UI
如下:

上部有一个“图表区域”,沿法线Y轴具有相同的比例尺,并带有标记和水平网格线。 在它下面是一条蠕动的线,其中沿X轴的时间戳为日期。
甚至更低的是所谓的“迷你地图”(如
Xcode 11
),即透明的“窗口”,它定义了我们“图表”时间段的那部分,在上方的“图表区域”中有更详细的介绍。 该“迷你地图”不仅可以沿
X
轴移动,而且其宽度可以更改,这会影响“图表区域”中的时间比例。
借助涂有“图表”颜色并带有其名称的
checkboxs
,您可以拒绝在“图表区域”中显示与该颜色相对应的“图形”。
有很多这样的“图形集”,例如,在我们的测试示例中,有5个,它们都应该位于一个屏幕上。
在使用
SwiftUI
设计的
UI
SwiftUI
无需按钮就可以在
Dark
模式和
SwiftUI
模式之间进行切换,这已经内置在
SwiftUI
。 此外,
SwiftUI
更多的组合“图表集”(即上面显示的屏幕集)的选项,而不是简单地向下滚动表格,我们将介绍其中一些非常有趣的选项。
但是首先,让我们专注于显示一个“
SwiftUI
集”,为此
SwiftUI
将
ChartView
创建一个
ChartView
:

SwiftUI
允许您以
SwiftUI
形式创建和测试复杂的
UI
,然后将这些块组装成拼图非常容易。 我们将这样做。 我们的
ChartView
很好地分成了以下小块:
GraphsForChart
这些是图本身,是为一个特定的“ GraphsForChart
”构建的。 用户使用“迷你地图” RangeView
控制的时间范围显示“图表”,将在下面显示。YTickerView
是具有高程和相应水平网格的Y
轴。IndicatorView
是一个水平用户驱动的指标,它允许您查看X
轴上时间轴上相应指标位置的“图表”值和时间。TickerView
“蠕变线”将X
轴上的时间戳显示为日期,RangeView
用户可以使用手势自定义的临时“窗口”,用于设置“图表”的时间间隔,CheckMarksView
包含以“图表”颜色着色的“按钮”,并允许您控制ChartView
上“ ChartView
”的存在。
用户可以通过三种方式与
ChartView
进行交互:
1.使用
DragGesture
手势控制“迷你地图”-它可以左右移动临时“窗口”并减小/增加其大小:

2.在水平方向上移动指示器,在固定的时间点显示“图表”的值:

3.使用以“图表”颜色上色并位于
ChartView
底部的按钮隐藏/显示某些“图表”:

我们可以以不同的方式组合各种“图表集”(我们在测试数据中有5个),例如,使用“
List
列表将它们全部同时放置在一个屏幕上(例如可上下滚动的表格):

或使用
ScrollView
和具有3D效果的
HStack
水平堆栈:

...或以
ZStack
形式叠加在一起
ZStack
“卡片”,其顺序可以更改:可以将带有“一组图表”的上层“卡片”下拉到足以看到下一张卡片的位置,如果继续将其向下拖动,则可以将其“拉下”转到“
ZStack
的最后一个位置,下一张”卡“”继续“:

在这些复杂的
UI
,一个“滚动表”,一个具有
3D
效果的水平堆栈,一个彼此叠置的
ZStack
“卡片”-所有用户交互方式都可以正常工作:沿着时间轴移动并更改
mini - map
,指示器和隐藏按钮的“比例” “图表”。
此外,我们将详细考虑使用
SwiftUI
进行此
UI
的设计-从简单元素到更复杂的元素。 但是首先,让我们了解一下我们拥有的数据结构。
因此,解决问题的方法分为以下几个阶段:
- 从
JSON
文件下载数据并以方便的“内部”格式显示 - 为一个“图表集”创建
UI
- 结合各种“图表集”
下载资料
Telegram提供了包含几个“图表集”的
JSON数据供我们使用。
chart
每个单独的“
chart
集”都包含
chart.columns
多个“图表”(或“线”)。 每个“图形”(“线”)在位置
0
处都有一个标记-
"x"
,
"y0"
,
"y1"
,
"y2"
,
"y3"
,然后是X轴上的任一时间值(“ x”) ,或
Y
轴上“ Graphics”(“线”)(
"y0"
,
"y1"
,
"y2"
,
"y3"
)的值:

“图表集”中所有“行”的存在是可选的。 “ column” x的值是UNIX时间戳(以毫秒为单位)。
此外,为
chart
每个“
chart
集”提供了以6个十六进制数字(例如,“#AAAAAA”)和
chart.names
格式的
chart.colors
颜色。
为了构建位于
JSON
文件中的数据模型,我使用了出色的
quicktype服务。 在此站点上,您从
JSON
文件中插入了一段文本,并指定了编程语言(
Swift
),结构名称(
Chart
),该语言将在“解析”此
JSON
数据之后形成。
屏幕的中央部分会生成一个代码,然后将其复制到名为
Chart.swift
的单独文件中,并将其复制到应用程序中。 我们将在此处放置JSON格式的数据模型。 使用数据加载器(从
JSON
文件到从
SwiftUI Generic
演示中借用的模型),我得到了一个
columns: [ChartElement]
数组
columns: [ChartElement]
,它是
Telegram
格式的“
columns: [ChartElement]
集”的集合。
包含异构元素数组的
ChartElement
数据
ChartElement
不太适合与图表进行密集的交互式工作,此外,时间戳以
UNIX
格式(以毫秒为单位)表示(例如
1542412800000, 1542499200000, 1542585600000, 1542672000000
),并且颜色以6进制格式表示数字(例如
"#AAAAAA"
)。
因此,在我们的应用程序内部,我们将使用相同的数据,但是使用不同的“内部”和相当简单的格式
[LinesSet]
。
[LinesSet]
数组是
LinesSet
“
LinesSet
Sets”的集合,其中每个包含格式为
"Feb 12, 2019"
xTime
"Feb 12, 2019"
(
X
轴)的
xTime
时间戳和几条“图表”
lines
(
Y
轴):

显示每个折线图(折线)的数据
- 整数
points: [Int]
, - 名为“图形”的
title: String
, - 类型“图形”
type: String?
, color : UIColor
Swift
UIColor
格式,- 点数
countY: Int
。
另外,根据
isHidden: Bool
的值,可以隐藏或显示任何“ Graph”。
upperBound
调整时间范围的
lowerBound
和
upperBound
的取值范围为
0
到
1
,不仅显示“迷你地图”时间窗口的大小(
upperBound
-
lowerBound
),而且还显示其在时间轴
X
上的位置:

JSON
数据结构
[ChartElement]
以及“内部”
LinesSet
和
Line
LinesSet
的数据结构位于
Chart.swift文件中。 用于加载
JSON
数据并将其转换为内部结构的代码位于
Data.swift文件中。 有关这些转换的详细信息可以在
这里找到。
结果,我们以内部格式接收了有关“图表集”的数据,这些数据是
chartsData
数组。

这是我们的数据
,但要在
SwiftUI
工作,必须确保用户对
chartsData
数组所做的任何更改(更改临时“窗口”,隐藏/显示“图表”)都导致
Views
自动更新。
我们将创建
@EnvironmentObject
。 这将使我们能够在需要的地方使用数据
,此外,如果数据发生更改,则自动更新
Views
。 这类似于
Singleton
或全局数据。
@EnvironmentObject
要求我们创建一些
final class UserData
,
final class UserData
位于
UserData.swift文件中,存储
chartsData
数据并实现
ObservableObject
协议:

@Published
“包装器”的存在将使您能够发布“新闻”,表明
UserData
类的
charts
的这些属性已更改,因此
SwiftUI
中任何“订阅此新闻”的
SwiftUI
都将能够自动选择新数据并进行更新。
回忆一下,在
charts
属性中,任何“
isHidden
”的
isHidden
值都可以更改(它们允许您隐藏或显示这些“图表”),以及每个“图表集”的下下限和上限上限时间限制。
我们希望在整个应用程序中使用
UserData
类的
charts
属性,并且不必借助
@EnvironmentObject
手动将其与
UI
同步。
为此,在启动应用程序时,我们必须创建
UserData ()
类的实例,以便随后我们可以在应用程序中的任何位置访问它。 我们将在
scene (_ : , willConnectTo: , options: )
方法内的
SceneDelegate.swift
文件中执行此操作。 这是我们创建和启动
ContentView
地方,在这里我们必须将
ContentView
传递
ContentView
我们
@EnvironmentObject
任何
@EnvironmentObject
,以便
SwiftUI
可以使它们可用于任何其他
View
:

现在,在任何
View
要访问
UserData
类的
@Published
数据,我们都需要使用
@EnvironmentObject
包装器创建
var
变量。 例如,在
RangeView
设置时间范围时
RangeView
我们使用
UserData
TYPE创建
var userData
变量:

因此,一旦我们在应用程序的“环境”中实现了某些
@EnvironmentObject
,我们就可以立即在最高级别或更低级别的10级别开始使用它-没关系。 但是更重要的是,每当
View
更改“环境”时,具有此
@EnvironmentObject
所有
Views
都会自动
@EnvironmentObject
,从而确保与数据的同步。
让我们继续设计用户界面(
UI
)。
一个“图形集”的用户界面(UI)
SwiftUI
提供了一种用于从许多小
Views
创建
SwiftUI
的复合技术,并且我们已经看到我们的应用程序非常适合该技术,因为它分为小块:“
ChartView
图表”
GraphsForChart
,“
GraphsForChart
图表”,
Y
轴
YTickerView
,由用户驱动的“图表”
IndicatorView
指标值,在
X
轴上带有
TickerView
“
TickerView
”
TickerView
,用户控制的“时间窗口”
RangeView
,隐藏/显示“ Charts”
CheckMarksView
。 我们不仅可以相互独立地创建所有这些
Views
,还可以使用
Previews
(
Previews
(初步的“实时”视图)对测试数据立即在
Xcode 11
进行测试。 您会惊讶于从其他更基本的
Views
创建代码的代码如此简单。
GraphView
“图形”(“线”)
我们将开始使用的第一个
View
实际上是“图形”本身(或“线”)。 我们将其称为
GraphView
:

像往常一样,创建
GraphView
首先使用菜单
File
→
New
→
File
在
Xcode 11
创建一个新文件:

然后我们选择所需的文件类型-这是
SwiftUI
文件:

...为我们的
View
命名为“ GraphView”并指出其位置:

单击
"Create"
按钮,并在屏幕中间获取带有
Text ( "Hello World!")
的标准
View
Text ( "Hello World!")
。

我们的任务是将
Text ("Hello World!")
替换为“ Graph”,但首先,让我们看一下创建“ Graph”所需要的初始数据:
- 我们有
line.points
“ Graphics” line: Line
, - 时间范围
rangeTime
,它是一个索引Range
X轴上Range
时间戳记Range
xTime
, - 值
rangeY: Range
Y rangeY: Range
“图形” rangeY: Range
, - “图形”描边线的粗细
lineWidth
。
将这些属性添加到
GraphView
结构中:

如果我们要用于“图形”
Previews
(预览)(仅适用于
MacOS Catalyna
,则必须使用索引范围
rangeTime
和“图形”本身的
line
数据启动
GraphView
:

我们已经具有从
chart.json
JSON
文件获得的
chartsData
测试数据,并将其用于
Previews
。
在我们的示例中,这将是该数据集中的
chartsData[0]
的第一个“
chartsData[0]
集”
chartsData[0]
和第一个“图表”,我们将提供
GraphView
作为
line
参数。
我们将使用索引
0..<(chartsData[0].xTime.count - 1)
的整个范围作为时间间隔
rangeTime
。
rangeY
和
lineWidth
可以在外部设置,也可以不设置,因为它们已经具有初始值:
rangeY
为
nil
,
lineWidth
为
1
。
我们故意将TYPE
rangeY
Optional
属性TYPE,因为如果未在外部设置
rangeY = nil
,而
rangeY = nil
,那么我们直接从
line.points
数据中计算“ Graphics”的最小
minY
和最大
maxY
值:

该代码可以编译,但是我们在屏幕上仍然有一个标准的
View
,在屏幕中间有文本
Text ("Hello World!")
:

因为在
body
我们必须用
Path
替换文本
Text ("Hello World!")
,这将使用
addLines(_:)
命令构建“ Graph:points:
line.points
”(几乎与
Core Graphics
类似)


我们将
Path
粗细为
lineWidth
Path
线对
stroke (...)
进行描边,描边的颜色将与默认颜色(即黑色)相对应:

我们可以用特定的“线条”
line.color
“颜色”中指定的颜色替换描边的黑色:

为了将“图形”放置在任何大小的矩形中,我们使用
GeometryReader
容器。 在
Apple
文档中
Apple
GeometryReader
是一个“容器”
View
,根据其自身
size
,坐标空间定义其内容。 本质上,
GeometryReader
是另一个
View
! 因为
SwiftUI
几乎所有
SwiftUI
都是
View
! 与其他
Views
不同,
GeometryReader
允许您访问一些其他有用的信息,您可以在设计自定义
View
时使用这些信息。
我们使用
GeometryReader
和
Path
容器来创建适用于任何大小的
GraphView
。 如果仔细看一下代码,我们将在
GeometryReader
的闭包中看到
GeometryReader
名为
geometry
GeometryReader
变量:

此变量具有
GeometryProxy
TYPE,它又是具有许多“意外”的
struct
结构:
public var size: CGSize { get } public var safeAreaInsets: EdgeInsets { get } public func frame(in coordinateSpace: CoordinateSpace) -> CGRect public subscript<T>(anchor: Anchor<T>) -> T where T : Equatable { get }
从
GeometryProxy
定义中,我们看到有两个计算变量
var size
和
var safeAreaInsets
,一个函数
frame( in:)
和一个
subscript getter
。 我们只需要
size
变量来确定“ Graphics”绘图区域的
geometry.size.width
的宽度和
geometry.size.width
的高度。
另外,我们使用
animation (.linear(duration: 0.6))
修改器
animation (.linear(duration: 0.6))
启用“图形”动画。

GraphView_Previews
允许我们非常简单地测试任何“集合”中的任何“图表”。 以下是索引为4的“图表集”中的“图表”:
chartsData[4]
和该集合中的索引为0“ Graphics”:
chartsData[4].lines[0]
:

我们使用
frame (height: 400)
height
“图形”的
height
设置为
frame (height: 400)
,宽度保持与屏幕的宽度相同。 如果我们不使用
frame (height: 400)
,那么“ Graph”将占据整个屏幕。
我们不指定值的范围rangeY
,并GraphView
使用值nil
是默认设置,在这种情况下,“计划”取其最小值和最大值的时间间隔rangeTime
:
尽管我们已经应用到我们的Path
修改animation (.linear(duration: 0.6))
,当你改变的范围内,不会出现动画,例如,rangeY
值“图形。” “图表”将简单地从范围的一个值“跳转”到另一个值rangeY
而无需任何动画。原因很简单:我们教过SwiftUI
如何在特定范围内绘制“图形” rangeY
,但我们没有教过SwiftUI
如何rangeY
在起点和终点之间的中间值的范围内多次再现“图形” ,为此SwiftUI
符合协议Animatable
。幸运的是,如果您View
是一个“数字”,即View
实现了一个协议Shape
,则该协议已经实现Animatable
。这意味着存在一个animatableData
可以控制动画过程的计算属性,但默认情况下将其设置为EmptyAnimatableData
,即不发生动画。为了解决动画问题,我们首先需要将“ Graph” GraphView
转换为Shape
。这很简单,我们只需要实现func path (in rect:CGRect) -> Path
我们已经拥有的功能,并在计算属性的帮助下指示animatableData
我们要制作动画的数据:
请注意,动画控件的主题是该主题的高级主题。SwiftUI
您可以在文章“高级SwiftUI动画-第1部分:路径”中了解更多信息。我们可以在带有动画的更简单的“图形”中使用生成的“ 图形”:您会发现我们不需要新的“图形” ,因为有了协议,我们的“图形” 将能够适应父级的任何大小。自然,我们得到的结果与情况相同:在以下组合中,我们将使用相同的“图形”来显示值。Graph
GraphViewNew

GeometryReader
GraphViewNew
Shape
Graph
View
Previews
GraphView

GraphViewNew
GraphsForChart
-一组“图表”(“线”)
此的目的View
-从“组图形”显示所有“图表”(“线”)chart
在预定的时间范围内rangeTime
具有共同的轴线Y
,该“线”的宽度是等于lineWidth
:
与GraphView
和GraphViewNew
我们将创建GraphsForChart
一个新文件GraphsForChart.swift
,并定义输入数据“图表集”:- “图表集”本身
chart: LineSet
(上的值 Y
), - “图表”时间戳的索引范围
rangeTime: Range
( X
), - 图线笔划粗细
lineWidth
值的范围rangeY: Range
为“一组曲线图的”( Y
)作为单独的范围的联合计算不隐藏(isHidden = false
)“图”,收录于“集”:
对于这一点,我们使用的功能rangeOfRanges
:
它没有隐藏,“图表”( isHidden = false
),我们将展示在ZStack
使用设计ForEach
,使每个“图形”都有可能出现在屏幕上,并使用“移动”修饰符离开屏幕transition(.move(edge: .top))
:
由于有了此修饰符,隐藏和返回“图形”的过程ChartView
将在屏幕上进行动画处理,并使用户清楚缩放比例的原因 Y
。使用drawingGroup()
意味着使用Metal
用于绘制图形形状。在我们的测试数据和模拟器上,您不会感觉到使用Metal
和绘制速度的差异 Metal
,但是如果您在任何图形上重现许多相当庞大的图形iPhone
,则您会注意到这种差异。有关更详细的介绍,请在使用时drawingGroup()
查看文章“高级SwiftUI动画-第1部分:路径”或观看视频会议237 WWDC 2019(使用SwiftUI构建自定义视图),就像使用预览进行GraphViewNew
测试一样GraphsForChart
,Previews
我们可以设置任何“图表集”,例如,使用索引0
:
IndicatorView
-水平移动的指示器“图形”。
该指标使您可以获取时间上相应点的“图表”和时间的确切值 X
:
该指标是为特定的“图表集”创建的chart
,包括沿着 X
带有MARK 的垂直LINE 沿“圆圈”的形式移动,以代替“图表”值。在此垂直线的顶部附加了一个小的“ POSTER”,其中包含“图表”和时间的数值。
指示器由用户使用手势滑动DragGesture
:
我们使用所谓的“增量”手势执行。而不是到起点的连续距离value.translation.width
,我们将onChanged
不断收到到上次在处理程序中执行手势的位置的距离:value.translation.width - self.prevTranslation
。这将为我们提供指标的平稳移动。为了在给定的“图表集” IndicatorView
的帮助下测试指标Previews
,chart
我们可以吸引现成View
的“图表”结构GraphsForChart
:
我们可以为rangeTime
指标IndicatorView
和“图表” 设置任意但相互协调的时间范围GraphsForChart
。这将使我们确保指示“图表”值的“圆圈”在正确的位置。TickerView
- X
带有标记。
到目前为止,我们的“图表”已被取消个性化,因为它们没有 X Y
适当的比例和标记。让我们在上面 X
加上时间戳TickerMarkView
。萨米标记TickerMarkView
是非常简单的View
垂直堆叠VStack
,其中布置Path
和Text
:
该组上为一定的“组图形”时间轴标记的chart : LineSet
形成TickerView
按照用户选择的时间范围rangeTime
和标记的近似量estimatedMarksNumber
,它必须是在视用户的字段:
用于布置我们使用ScrollView
水平堆栈的“运行”时间戳HStack
,
rangeTime
.
TickerView
step
,
TimeMarkView
,
rangeTime
widthRange
…

… c
step
chart.xTime
indexes
.
X
— —
overlay
…

…
HStack
,
TimeMarkView
,
offset
:

,
X
-
colorXAxis
, —
colorXMark
:

YTickerView
— Y
.
这View
幅画 Y
带有数字标记YMarkView
。标记本身YMarkView
是非常简单的View
垂直堆叠VStack
在布置Path
(水平线)和Text
与多个:
关于该组标记 Y
为一定的“组图形” chart
形成YTickerView
。值rangeY
的范围是通过使用函数将此“图表集”中包含的所有“图表”的值范围的并集计算得出的rangeOfRanges
。在Y轴标记的近似数由参数设定estimatedMarksNumber
:
在YTickerView
我们监控值的“图形”的变化范围rangeY
。实际上,Y轴-垂直线-我们overlay
在标记上加上了...
另外,我们可以设置Y轴本身的颜色colorYAxis
和-标记colorYMark
:
RangeView
-使用“迷你地图”设置时间范围。
我们的用户界面的大多数移动部分是设定的时间范围(lowerBound
,upperBound
),以显示“图形设置”:
RangeView
-一种mini - map
用于在其他“图形组”的更详细的审议分配一定的时间段Views
。与前面的一样View
,的初始数据为RangeView
:
- “图表集”本身
chart: LineSet
(值Y
), - 高度
height
"mini-map"
RangeView
, - 宽度
widthRange
"mini-map"
RangeView
, - 缩进
indent
"mini-map"
RangeView
。
与上述其他方法不同Views
,我们必须用手势更改DragGesture
时间范围(lowerBound
,upperBound
)并立即查看其更改,因此我们将使用的用户定义的时间范围(lowerBound
,upperBound
)存储在变量变量中@EnvironmentObject var userData: UserData
:
对变量的任何更改var userData
都将导致重画所有Views
依赖他的东西。其中的主要字符RangeView
是透明的“窗口”,其位置和大小由用户使用手势控制DragGesture
:1.如果在透明的“窗口”中使用手势,则“窗口”的位置会随之变化 X
,并且其大小不会改变:
2.如果在左侧较暗的部分中使用手势,则仅“窗口”的左边界会改变lowerBound
,从而允许我们减小或增加透明“窗口”的宽度:
3.如果在右侧较暗的部分中使用手势,则仅“窗口”的右侧边界会改变upperBound
,允许增加或减少透明的“窗口”的宽度:
RangeView
由3个主要元件是很简单的:两个矩形Rectangle ()
和图象Image
,其边界由特性所决定lowerBound
,并upperBound
从@EnvironmentObject var userData: UserData
与由姿势被控制的DragGesture
:
我们“叠加”在这种结构中(overlay
)是已经熟悉我们GraphsForChartView
给定的“一套图形”的“图形” chart
:
这将使我们能够监视有多少“图表”进入“窗口”。在透明的“窗口”的任何变化(这完全是移动或边界的变化)的属性变化的结果lowerBound
,并upperBound
在用户数据中的函数onChanged
符号处理DragGesture
在两个箱子Rectangle ()
和画面Image
......
这是,正如我们已经知道,会自动导致重绘其他Views
(在这种情况下, “图表”,带有标记的X轴,带有标记和指示符的Y轴c hartView
):
由于我们View
的变量包含@EnvironmentObject userData: UserData
,对于预览Previews
,我们必须使用设置其初始值.environmentObject (UserData())
:
CheckMarksView
-“隐藏”并显示“图表”。
CheckMarksView
它是一个水平堆栈HStack
,其中一行checkBoxes
用于切换isHidden
“图形集”中每个“图形” 的属性chart
:
CheckBox
在我们的项目中,可以使用常规按钮Button
调用CheckButton
或使用模拟按钮来实现SimulatedButton
。必须模仿
该按钮Button
,因为将这些按钮中的几个放置List
在层次结构中较高的位置时,它们“拒绝”正常工作。自从Beta 1到当前版本以来,这是一个长期存在的错误,一直存在于Xcode 11中。该应用程序的当前版本使用模拟按钮SimulatedButton
。模拟按钮SimulatedButton
和真实按钮CheckButton
使用相同的东西View
作为“外观”- CheckBoxView
。其中HStack
包含Tex
和Image
:
请注意,初始化参数CheckBoxView
是一个@Binding
变量var line: Line
。该物业isHidden
该变量决定了“亮相” CheckBoView
:
当使用CheckBoView
在SimulatedButton
和CheckButton
你需要使用该商标$
的line
初始化:
的属性isHidden
变量line
切换为SimulatedButton
使用onTapGesture
...
...但CheckButton
-与通常action
的按钮Button
:
请注意,初始化参数SimulatedButton
,并且CheckButton
还@Binding
一个变量var line: Line
。因此,它们的使用应适用$
的CheckMarksView
转换变量userData.charts[self.chartIndex].lines[self.lineIndex(line: line)].isHidden
,它存储在一个变量全局变量@EnvironmentObject var userData
:
我们一直保持未使用的项目目前CheckButton
的情况下,如果你突然Apple
将纠正这个错误。另外,您可以尝试使用CheckButton
in CheckMarksView
来SimulatedButton
确保在ChartView
使用List
c 组成多个“图表集”的情况下它不起作用ListChartsView
。由于我们View
的变量包含一个变量@EnvironmentObject var userData: UserData
,为了进行预览Previews
,我们必须使用以下命令设置其初始值.environmentObject(UserData())
:
各种组合Views
。
SwiftUI
-主要是将各种小游戏组合Views
成大型游戏,将大游戏组合Views
成非常大的游戏,等等Lego
。由于SwiftUI
有各种各样的方法,例如组合Views
:- 的垂直堆叠
VStack
, - 水平叠层
HStack
, - 堆栈的“深度”
ZStack
, - 组
Group
, ScrollView
,- 列表
List
, - 形成
Form
, - 书签容器
TabView
- 等
我们从最简单的组合开始,该组合GraphsViewForChart
提供“无面的”“图表集” GraphsForChart
AXIS Y和使用“深度”堆栈的指示器沿X轴移动ZStack
:
我们在Previews
新GraphsViewForChart
容器中添加了一个新容器NavigationView
,以便Dark
使用修饰符在模式下显示它.collorScheme(.dark)
。我们继续进行组合,并使用AXIS Y和以“运行线”形式显示的指示器AXIS X附加到上面获得的“图表集”,以及控件:“迷你地图”时间范围RangeView
和CheckMarksView
“图表”显示开关。结果,我们得到了上述内容ChartView
,它显示了“一组图表”,并允许您在时间轴上控制其显示:
在这种情况下,我们使用垂直堆栈执行组合VStack
:
现在,我们将考虑3个选项,用于组合已接收的ChartView“图表集”:- “可滚动表格”
List
, HStack
具有3D效果的水平堆叠,ZStack
叠加的“卡片”
“可滚动表”ListChartsView
使用列表进行组织List
:
具有3D效果的ScrollView
水平堆栈使用水平堆栈HStack
和以下形式的列表进行组织ForEach
:
在此视图中,所有用户交互方式都可以正常工作:沿着时间轴移动并更改“比例” mini- map
,指示符和隐藏按钮“图表”。ZStack
叠加的“卡片”。
首先,我们CardView
为“地图” 创建-这是带有AXIS X和Y的“图表集”,但是没有控件:没有“迷你地图”和没有用于控制图表外观/隐藏的按钮。CardView
与ChartView
“卡片” 非常相似,但由于我们要将“卡片”彼此叠加,因此我们需要它们不透明。为此,我们ZStack
在“背景”中使用了其他颜色cardBackgroundColor
。此外,我们会做的“地图”与圆边的盒子:
的“地图”由堆栈组织的叠加VStack
,ZStack
并在窗体列表ForEach
:
但是,我们将对彼此不只是一个“卡”和“3D-macshtabiruemye”卡CardViewScalable
的大小随索引的增加而减小indexChat
.
«3D-c » (
sequenced
)
LongPressGesture
DragGesture
, «»
indexChat == 0
:

(
LongPress
) «» « », (
Drag
) , , , «»
ZStack
, «» «»:

«»
TapGesture
,
LongPressGesture
DragGesture
:

Tap
« »
ChartView
RangeView
CheckMarksView
:

TabView
在一个屏幕上组合组成“图表集”的所有3个变体的应用程序ChartView
。
我们有3个带有图像Image
和文本的书签,它们的联合展示不需要Text
垂直堆叠VStack
。它们对应于我们的3种组合“图表集”的方法ChartViews
:- “可滚动表格”
ListChartViews
, - 具有3D效果的水平堆栈
HStackChartViews
, - ZStack叠加了“卡片”
OverlayCardsViews
。
用户交互的所有元素:沿着时间轴移动,并通过帮助mini - map
,指示器和按钮更改“比例” 以隐藏“图表”。在所有3种情况下都能正常工作。代码在Github上。SwiftUI
...
您应该熟悉视频教程,书籍和博客:Mang To,让我们构建该应用程序,以及一些SwiftUI应用程序的说明,这是一本免费的书“以示例为单位的SwiftUI ”和一本视频www.hackingwithswift.com/quick-start/swiftui付费的书但其中一半可以免费下载www.bigmountainstudio.com/swiftui-views-book-SwiftUI的100天课程www.hackingwithswift.com/articles/201/start-the-100-days-swiftui,从现在开始结束2019年12月31日,-在SwiftUI令人印象深刻的东西上进行swiftui-lab.com- 马吉德博客,-上pointFree.cowww.pointfree.co关于在SwiftUI中使用Reducers的文章的 “马拉松”(超级有趣)是一个很棒的MovieSwiftUI应用程序,它借鉴了一些想法。