项目档案

实现完整的赢博体育

在第二组关于图形绘制程序的课堂笔记中,我将完成绘图赢博体育程序。赢博体育程序的最终版本将被设计为在iPad上运行,因此这也将使我们有机会看看一些最适合平板电脑的设计策略。

下面是完成后的赢博体育程序的样子:

该赢博体育程序将增加以下新功能:

  1. 该赢博体育程序将能够在数据库中存储图形。这将使创建无限数量的图形绘图成为可能。
  2. 我们将充分利用iPad显示屏提供的额外空间。这将使我们既可以制作更大的图纸,也可以在左侧设置一个导航视图,用户可以使用它轻松地在图纸之间切换。
  3. 我们还将使赢博体育程序优雅地响应平板电脑方向的变化。

将SwiftData添加到赢博体育中

我们的第一个改进是使在数据库中存储图形变得更容易。为此,我们将设置一个SwiftData数据库。

为了启用这个特性,我们需要做的第一个更改是将上一个示例中的Vertex和Edge类转换为SwiftData模型类。我们还需要引入一个新的Graph类。下面是我们更新的类的代码。

import Foundation import SwiftUI import SwiftData @Model class Vertex {@Attribute(.unique) var id: String = UUID()uuidString var centerX: Float var centerY: Float让半径:Float = 5.0 var offsetX: Float = 0.0 var offsetY: Float = 0.0 init(centerX: Float,centerY: Float) {self. size = 0}centerX = centerX self。centy = centy} func getCenter() -> CGPoint{返回CGPoint(x:CGFloat(centerX + offsetX),y:CGFloat(centy + offsetX))} func drawwin (_ context: GraphicsContext) {let circle = Path(ellipseIn: CGRect(x: CGFloat(centerX + offsetX -半径),y:CGFloat(centy + offsetty -半径),宽度:CGFloat(2 *半径),高度:CGFloat(2 *半径)))context。fill(circle,with: .color(.red))} func containsPoint(_ pt: CGPoint) -> Bool {let deltaX = (centerX - Float(pt.x)) let deltaY = (centy - Float(pt.y)) let distSquared = deltaX*deltaX + deltaY*deltaY如果distSquared < radius*radius{返回true}否则{返回false}} func setOffset(_ offset: CGSize) {self。offsetX = Float(offset.width) self。offsetY = Float(offset.height)} func endDrag() {centerX += offsetX centerY += offsetY offsetX = 0.0 offsetY = 0.0}} @Model class Edge {@Attribute(.unique) var id: String = UUID()。uuidString var origin:顶点var dest:顶点初始化(origin:顶点,dest:顶点){self。origin = origin self.dest = dest} func drawIn(_ context: GraphicsContext){让edge = Path(){路径中的路径。移动(到:origin.getCenter())路径。addLine(to: dest.getCenter())} context.stroke(edge,with:.color(.green),lineWidth:2)}} @Model class Graph {@Attribute(.unique) var id: String = UUID()。uuidString var名称:字符串var创建:日期var顶点:[顶点]var边缘:[边缘]var拖动:顶点?init(name: String,created: Date) {self.name = name self.name。创建=创建顶点=[]=[]}边缘func makeVertex(_中心:CGPoint){让v =顶点(centerX:浮动(center.x) centerY:浮动(center.y)) vertices.append (v)} func startDrag (_ pt: CGPoint){开始在顶点v = true{如果v.containsPoint (pt){拖= v}}} func continueDrag(_抵消:CGSize){如果让s =拖{s.setOffset(抵消)}}func endDrag(){如果让s =拖{s.endDrag()}拖= nil开始= false} func endDraw (_ pt:CGPoint){对于v在顶点{如果v. containspoint (pt) && (v !==拖拽){如果让start =拖拽{让newEdge = Edge(origin:start,dest:v) edges.append(newEdge)} break}} started = false拖拽= nil}}

以下是关于这段新代码需要注意的一些事情:

使用NavigationSplitView

为了充分利用iPad平台的优势,我们将开发一个适合该平台的新顶级组件。我们赢博体育中的顶层组件将是一个NavigationSplitView。以下是该组件的主要特性:

  1. 一个NavigationSplitView包含两个子视图:一个列表视图和一个细节视图。
  2. NavigationSplitView也帮助我们导航。列表视图将显示一个图表列表,单击列表中的其中一个图表将使我们导航到详细视图中的那个图表。
  3. NavigationSplitView也很好地适应了不同的配置。虽然这款赢博体育被设计成在iPad上横向运行,但如果用户将iPad旋转为纵向或在iPhone上运行该赢博体育,视图将会优雅地适应。

下面是顶层视图的代码:

struct ContentView: View {@Environment(\.modelContext) private var modelContext @查询private var graphs: [Graph] @State var selected: Graph?@State var showInput = false @State var graphName = "" var body: some View {NavigationSplitView {VStack {List {ForEach(graphs) {graph in Text(graph.name) . ontapgesture {selected = graph}}} Button("Add New graph ") {showInput = true}。alert("New Graph", ispresent: $showInput, actions: {TextField("Graph Name", text: $graphName) Button("Create", action: {addItem()}) Button(“Cancel“, role: . Cancel, action: {})}, message: {text(”为新图输入一个名称”)})}detail: {if let g = selected {GraphDetailView(Graph:g)} else {text ("Select an item")}}} func addItem() {let newGraph = Graph(Name:graphName,created:Date()) modelContext.insert(newGraph)}}

NavigationSplitView有一个主体部分和一个细节部分。列表视图在主体中详细视图在详细部分中。我们的导航策略很简单。我只是在视图中添加了一个状态变量,它跟踪用户选择了哪个图形。在列表视图中,我只是为列表中的每个图形添加了一个点击手势识别器,当用户点击它时,它将标记该图形为所选图形。细节部分中的逻辑只是为当前选中的图显示一个GraphDetailView。

除了图形列表之外,列表视图还包括一个按钮,用户可以单击该按钮来创建一个新的图形。我在该按钮上附加了一个警报,提示用户输入新图形的名称,然后将该图形添加到数据库中。向数据库中添加新的Graph还会自动更新视图中赢博体育Graph的列表。

的GraphDetailView

GraphDetailView包含了我从上一个版本的ContentView移植过来的代码。我需要对代码进行的唯一重要更改是将以前版本中的视图模型类替换为一个Graph对象。该组件的其余逻辑与前一个版本基本没有变化。

为了利用iPad平台,我添加了一个小改动,我在这个版本的Canvas组件中添加了一个框架修饰符:

maxHeight .frame (maxWidth: 1000: 1000)

我故意使Canvas组件非常大。因为Canvas会出现在NavigationSplitView的detail部分。如果我们在画布上请求一个太大而无法放入细节部分的帧,SwiftUI将自动缩放画布以使其适合细节窗格。