
我将首先观察到,本文中讨论的应用程序如果要使用
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部分:路径”中了解更多信息。我们可以在带有动画的更简单的“图形”中使用生成的“ 图形”:您会发现我们不需要新的“图形” ,因为有了协议,我们的“图形” 将能够适应父级的任何大小。自然,我们得到的结果与情况相同:在以下组合中,我们将使用相同的“图形”来显示值。GraphGraphViewNew
GeometryReaderGraphViewNewShapeGraphViewPreviewsGraphView
GraphViewNewGraphsForChart -一组“图表”(“线”)
此的目的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将纠正这个错误。另外,您可以尝试使用CheckButtonin CheckMarksView来SimulatedButton确保在ChartView使用Listc 组成多个“图表集”的情况下它不起作用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提供“无面的”“图表集” GraphsForChartAXIS 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应用程序,它借鉴了一些想法。