我们的下一个示例是一个简单的赢博体育程序,用于存储部门中一组人员的目录。该赢博体育程序存储了一个人员列表,每个人都有名字和办公室。
在本例中,我们将获得更多使用屏幕间导航的经验,我们还将深入了解显示和管理项目列表的技术。在这个场景背后,这个例子也将是我们第一次接触到模型类的使用,这是赢博体育程序的一个架构特性,我们将在未来的例子中广泛使用。
赢博体育程序中的第一个视图显示目录。
单击导航栏中的+按钮将用户带到第二个视图,用户可以在其中输入新用户的详细信息。
此赢博体育程序将维护一个人员目录,该目录将存储在本地文件中。
目录中的单个条目由Person对象表示。
class Person:可编码,可识别{var id = UUID() var name: String var office: String init(name n: String,office o: String) {name = n;办公室= 0;}}
这里一个非常重要的细节是,我们使用class来声明这个类,而不是通常的struct。Swift允许我们通过使用struct或class来设置类。这两种形式之间的重要区别是,用struct声明的类是按值对象传递的,而用class声明的类总是通过引用传递来处理。这对我们的代码有微妙而重要的影响。这种区别很重要的地方是在创造一个新人的过程中。要创建一个新的Person,我们将首先创建一个具有空名称和空地址的初始对象。然后,我们将新的Person对象追加到我们正在存储的人员列表中。如果我们使用struct来声明Person类,那么调用append()方法(将这个新Person放到人员列表中)将按值传递这个新Person,这将导致将空Person的副本放到列表中。另一方面,如果用class声明Person类,则append()方法会将对新创建的Person的引用放在列表中。如果用户随后为我们的新Person提供了姓名和办公室,那么这些更改将自动赢博体育于人员列表末尾的新Person,因为该列表将存储对新Person的引用。
在这里使用class的另一个后果是,我们现在有义务为我们的类提供显式构造函数。这个构造函数就是上面看到的init()方法。
为了能够在SwiftUI列表中显示Person对象,我们需要满足每个对象都有唯一id的要求。我们通过让我们的Person类实现可识别协议并给我们的Person类一个id属性来满足这个需求。UUID()函数根据需要为每个人生成唯一标识符。
我们赢博体育程序的数据模型类是Directory类。目录存储了一个Person对象列表,并包含了读取和写入赢博体育程序数据到文件的方法。更具体地说,我们将使用一种称为属性列表文件的特殊文件类型来存储数据,因为Swift对这种文件类型提供了很好的支持。
类目录:ObservableObject {@Published var people: [Person] @Published var newPerson: Person = Person(name:"",office:"")让itemArchiveURL: URL ={让documentsDirectories = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)让documentDirectory = documentsDirectories.first!return documentDirectory.appendingPathComponent("directory.plist")}() init() {do{让data = try data (contentsOf: itemArchiveURL)让unarchiver = PropertyListDecoder()让loc = try unarchiver.decode(Array<Person>.self, from:数据)people = loc} catch {people = [Person(姓名:“Acacia Ackles”,办公室:“Steitz 131”),Person(姓名:“Joe Gregg”,办公室:“Briggs 413”),Person(姓名:“Kurt Krebsbach”,办公室:“Briggs 411”)]}}func makeNewPerson() {newPerson = Person(姓名:“”,办公室:“”)people.append(newPerson)} func delete(at offsets: IndexSet) {people。remove(atOffsets: offsets)} func saveChanges() {do{让encoder = PropertyListEncoder()让data = try encoder.encode(people)尝试数据。write(to: itemArchiveURL)} catch {}}}
除其他事项外,该类包含用于将Person对象列表保存到属性列表文件并稍后从该文件读取这些对象的代码。这样做的第一步是设置一个指向该文件的特殊URL。您可以在itemArchiveURL属性的初始化器中看到设置URL的代码。iOS中的每个赢博体育程序都有一个与之关联的私有存储空间。在这个私有空间中有一个文档目录。这里的代码首先获取指向该文档目录的URL,然后附加我们想要使用的文件的名称‘directory.plist’。plist扩展名将此文件标识为属性列表文件。
上面的init()方法将在我们创建新的Directory对象时自动调用,它包含尝试打开目录的代码。plist文件作为数据对象,然后将该数据对象传递给一个特殊的PropertyListDecoder对象,该对象将获取数据对象的内容并将其转换为Person对象数组。为了使这个过程正常工作,我们需要Person类来实现可编码接口。
如果我们是第一次运行赢博体育程序,将没有目录。Plist文件和打开该文件的代码将失败,并出现异常。为了处理这个问题,我将文件读取代码放在do/catch块中。在文件读取过程中出现异常的情况下,catch块包含创建一些示例数据供我们使用的代码。
这里还有一个saveChanges()方法,它可以将人员列表保存回文件。这与读取代码类似,但使用PropertyListEncoder将数据转换为正确的格式以进行保存。
到目前为止,我们已经看到视图使用了@State变量。@State变量的一个特殊特性是它们会自动监视更改。改变@State变量的值会导致包含该变量的视图被重绘。
当我们将Directory数据模型对象放在视图中时,我们将不再使用带有存储该对象的属性的@State。相反,我们将使用另一种方法:
@Published
。就像@State
属性,@Published
属性由SwiftUI观察。更改@Published
属性将触发依赖于它们的视图的重绘。@ObservedObject
或@StateObject
房地产包装。这告诉SwiftUI对象所包含的内容@Published
系统需要监视其变化的属性。下面是赢博体育程序中第一个视图的代码。这个视图设置导航控制器和该控制器中显示的第一个视图。第一个视图显示目录中的人员列表。
struct ContentView: View {@StateObject var directory: directory = directory () @State var isShowingDetailView = false var body: some View {NavigationStack {VStack {List {ForEach(directory.people) {p in HStack {Text(p.name) Spacer() Text(p.office)}}. ondelete (perform:delete)}}。navigationDestination(ispresenting: $isShowingDetailView, destination: {PersonView(d:directory)}) .navigationTitle(" directory ") .navigationBarTitleDisplayMode(.inline) .navigationBarItems(尾部:Button("+")){directory. makenewperson () isShowingDetailView = true;})}} function delete(at offsets: IndexSet) {directory.delete(at:offsets) directory.saveChanges()}}
这里我们将模型对象存储在目录属性中。
要在目录中创建新条目,用户将单击导航栏中的+按钮。通常我们会使用NavigationLink来实现+按钮。在本例中,我们使用按钮代替。这样做的原因是,NavigationLink只是将您移动到一个新视图:如果您需要在进行导航之前执行其他操作,则需要一个按钮的操作闭包来完成此操作。
为了实现+按钮中的导航操作,我们使用这个策略。
isShowingDetailView
属性切换为true。的目的地:
参数设置到第二个视图的导航。第一个视图的主要目的是显示Person对象列表。在目录对象中有一个已发布属性people,它包含Person对象列表。在我们的用户界面中,我们设置了一个List()组件,其主体闭包包含一个ForEach()表达式,用于显示directory.people中的赢博体育Person对象。具体来说,对于列表中的每个Person p,我们将创建一个HStack,其中显示该人的姓名、间隔符和该人的办公室。
这个视图演示的另一个特性是列表项的删除手势。在设置列表项的代码中,我使用了onDelete(perform:)修饰符。当用户在列表条目上向左滑动以请求删除它时触发onDelete事件。当用户请求删除列表项时,这将调用视图的delete()方法。
第二个视图允许用户输入他们刚刚创建的新人员的详细信息。
struct PersonView: View {@StateObject var d: Directory var body: some View {VStack {HStack {Text("Name:").frame(width:60,height:20,alignment: .trailing) TextField("", Text:$d. newperson . Name).textFieldStyle(.roundedBorder)} HStack {Text("Office:").frame(width:60,height:20,alignment: .trailing) TextField("", Text:$d. newperson . Office).textFieldStyle(.roundedBorder)} Button("Save Changes"){d. savechanges ()}.buttonStyle(.borderedProminent).padding()}}}
第一个视图中+按钮的代码调用Directory的makeNewPerson()方法。这将创建一个新的Person对象,并将这个新的Person存储在Directory的newPerson属性中,以便于访问。第二个视图将访问newPerson属性,并将其名称和办公室属性链接到两个文本字段,用户可以在其中输入该数据。
第二个视图还包括一个“保存更改”按钮。单击此按钮将调用Directory的saveChanges()方法,该方法将Directory中存储的Person对象的完整列表保存到属性列表文件中。
因为我们是在由第一个视图设置的NavigationStack的上下文中显示第二个视图,所以我们将获得一个方便的导航特性。不需要设置它,我们将在第二个视图的导航栏中得到一个NavigationLink,上面写着‘Directory’。单击此按钮将使用户回到第一个视图。