上一次比赛分配的SwiftUI电报图表(2019年3月):一切都很简单



我将首先观察到,本文中讨论的应用程序如果要使用Live Previews需要Xcode 11MacOS Catalina,如果要使用模拟器则需要Mojave 。 应用程序代码在Github上

Apple今年在WWDC 2019上宣布了SwiftUI ,这是一种在所有Apple设备上构建用户界面(UI)的新声明方式。 这几乎与通常的UIKit完全不同,而且我-和许多其他开发人员一样-确实希望看到这个新工具的实际应用。

本文介绍了使用SwiftUI解决问题的经验,该问题的UIKit的代码无比复杂,在我看来无法以可读的方式阅读。

该任务与上一次针对AndroidiOSJS开发人员的Telegram竞赛有关,该竞赛于2019年3月10日至3月24日举行。 在这场比赛中,提出了一个简单的任务,即根据时间基于JSON数据以图形方式显示Internet上某种资源的使用强度。 作为iOS开发人员,您应该使用Swift将从头编写的代码提交给竞争对手,而无需使用任何无关的专业库进行绘图。

此任务需要具备使用iOS图形和动画功能的技能: 核心图形核心动画金属OpenGL ES 。 其中一些工具是底层的,非面向对象的编程工具。 本质上,在iOS中,没有乍看之下可用于解决图形任务的模板。 因此,每个参赛者都根据MetalCALayersOpenGLCADisplayLink发明了自己的动画器( 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集”,为此SwiftUIChartView创建一个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]数组是LinesSetLinesSet Sets”的集合,其中每个包含格式为"Feb 12, 2019" xTime "Feb 12, 2019"X轴)的xTime时间戳和几条“图表” linesY轴):



显示每个折线图(折线)的数据

  • 整数points: [Int]
  • 名为“图形”的title: String
  • 类型“图形” type: String?
  • color : UIColor Swift UIColor格式,
  • 点数countY: Int

另外,根据isHidden: Bool的值,可以隐藏或显示任何“ Graph”。 upperBound调整时间范围的lowerBoundupperBound的取值范围为01 ,不仅显示“迷你地图”时间窗口的大小( upperBound - lowerBound ),而且还显示其在时间轴X上的位置:



JSON数据结构[ChartElement]以及“内部” LinesSetLine LinesSet的数据结构位于Chart.swift文件中。 用于加载JSON数据并将其转换为内部结构的代码位于Data.swift文件中。 有关这些转换的详细信息可以在这里找到。

结果,我们以内部格式接收了有关“图表集”的数据,这些数据是chartsData数组。



这是我们的数据 ,但要在SwiftUI工作,必须确保用户对chartsData数组所做的任何更改(更改临时“窗口”,隐藏/显示“图表”)都导致Views自动更新。

我们将创建@EnvironmentObject 。 这将使我们能够在需要的地方使用数据 ,此外,如果数据发生更改,则自动更新Views 。 这类似于Singleton或全局数据。

@EnvironmentObject要求我们创建一些final class UserDatafinal 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图表”, YYTickerView ,由用户驱动的“图表” IndicatorView指标值,在X轴上带有TickerViewTickerViewTickerView ,用户控制的“时间窗口” RangeView ,隐藏/显示“ Charts” CheckMarksView 。 我们不仅可以相互独立地创建所有这些Views ,还可以使用PreviewsPreviews (初步的“实时”视图)对测试数据立即在Xcode 11进行测试。 您会惊讶于从其他更基本的Views创建代码的代码如此简单。

GraphView “图形”(“线”)


我们将开始使用的第一个View实际上是“图形”本身(或“线”)。 我们将其称为GraphView



像往常一样,创建GraphView首先使用菜单FileNewFileXcode 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
rangeYlineWidth可以在外部设置,也可以不设置,因为它们已经具有初始值: rangeYnillineWidth1

我们故意将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时使用这些信息。

我们使用GeometryReaderPath容器来创建适用于任何大小的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 sizevar 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



GeometryReaderGraphViewNewShapeGraphView

PreviewsGraphView



GraphViewNew

GraphsForChart -一组“图表”(“线”)


此的目的View-从“组图形”显示所有“图表”(“线”)chart在预定的时间范围内rangeTime具有共同的轴线Y,该“线”的宽度是等于lineWidth



GraphViewGraphViewNew我们将创建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测试一样GraphsForChartPreviews我们可以设置任何“图表集”,例如,使用索引0



IndicatorView -水平移动的指示器“图形”。


该指标使您可以获取时间上相应点的“图表”和时间的确切值 X



该指标是为特定的“图表集”创建的chart,包括沿着 X带有MARK 垂直LINE 沿“圆圈”的形式移动,以代替“图表”值。在此垂直线的顶部附加了一个小的“ POSTER”,其中包含“图表”和时间的数值。



指示器由用户使用手势滑动DragGesture



我们使用所谓的“增量”手势执行。而不是到起点的连续距离value.translation.width,我们将onChanged不断收到到上次在处理程序中执行手势的位置的距离:value.translation.width - self.prevTranslation这将为我们提供指标的平稳移动。

为了在给定的“图表集” IndicatorView的帮助下测试指标Previewschart我们可以吸引现成View的“图表”结构GraphsForChart



我们可以为rangeTime指标IndicatorView和“图表” 设置任意但相互协调的时间范围GraphsForChart这将使我们确保指示“图表”值的“圆圈”在正确的位置。

TickerView- X带有标记。


到目前为止,我们的“图表”已被取消个性化,因为它们没有 X Y适当的比例和标记。让我们在上面 X加上时间戳TickerMarkView。萨米标记TickerMarkView是非常简单的View垂直堆叠VStack,其中布置PathText



该组上为一定的“组图形”时间轴标记的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 :



YTickerViewY .


View幅画 Y带有数字标记YMarkView。标记本身YMarkView是非常简单的View垂直堆叠VStack在布置Path(水平线)和Text与多个:



关于该组标记 Y为一定的“组图形” chart形成YTickerView。值rangeY的范围是通过使用函数将此“图表集”中包含的所有“图表”的值范围的并集计算得出的rangeOfRanges。在Y轴标记的近似数由参数设定estimatedMarksNumber



YTickerView我们监控值的“图形”的变化范围rangeY。实际上,Y轴-垂直线-我们overlay在标记上加上了...



另外,我们可以设置Y轴本身的颜色colorYAxis和-标记colorYMark



RangeView -使用“迷你地图”设置时间范围。


我们的用户界面的大多数移动部分是设定的时间范围(lowerBoundupperBound),以显示“图形设置”:



RangeView-一种mini - map用于在其他“图形组”的更详细的审议分配一定的时间段Views

与前面的一样View,的初始数据为RangeView



  • “图表集”本身chart: LineSet(值Y),
  • 高度height "mini-map" RangeView
  • 宽度widthRange "mini-map" RangeView
  • 缩进indent "mini-map" RangeView

与上述其他方法不同Views,我们必须用手势更改DragGesture时间范围(lowerBoundupperBound)并立即查看其更改,因此我们将使用的用户定义的时间范围(lowerBoundupperBound)存储在变量变量中@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包含TexImage



请注意,初始化参数CheckBoxView是一个@Binding变量var line: Line。该物业isHidden该变量决定了“亮相” CheckBoView




当使用CheckBoViewSimulatedButtonCheckButton你需要使用该商标$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 CheckMarksViewSimulatedButton确保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



我们在PreviewsGraphsViewForChart容器中添加了一个容器NavigationView,以便Dark使用修饰符模式下显示它.collorScheme(.dark)

我们继续进行组合,并使用AXIS Y和以“运行线”形式显示的指示器AXIS X附加到上面获得的“图表集”,以及控件:“迷你地图”时间范围RangeViewCheckMarksView“图表”显示开关

结果,我们得到了上述内容ChartView,它显示了“一组图表”,并允许您在时间轴上控制其显示:



在这种情况下,我们使用垂直堆栈执行组合VStack



现在,我们将考虑3个选项,用于组合已接收的ChartView“图表集”:

  1. “可滚动表格” List
  2. HStack具有3D效果的水平堆叠
  3. ZStack 叠加的“卡片”

“可滚动表”ListChartsView使用列表进行组织List



具有3D效果ScrollView水平堆栈使用水平堆栈HStack和以下形式的列表进行组织ForEach



在此视图中,所有用户交互方式都可以正常工作:沿着时间轴移动并更改“比例” mini- map,指示符和隐藏按钮“图表”。

ZStack 叠加的“卡片”。


首先,我们CardView为“地图” 创建-这是带有AXIS X和Y的“图表集”,但是没有控件:没有“迷你地图”和没有用于控制图表外观/隐藏的按钮。CardViewChartView“卡片” 非常相似,但由于我们要将“卡片”彼此叠加,因此我们需要它们不透明。为此,我们ZStack在“背景”中使用了其他颜色cardBackgroundColor。此外,我们会做的“地图”与圆边的盒子:



的“地图”由堆栈组织的叠加VStackZStack并在窗体列表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

  1. “可滚动表格” ListChartViews
  2. 具有3D效果的水平堆栈HStackChartViews
  3. 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应用程序,它借鉴了一些想法。

Source: https://habr.com/ru/post/zh-CN468897/


All Articles