IOS18 编程初学者指南(二)

原文:zh.annas-archive.org/md5/c79b480579d21d3ef3e2083c44cdee36

译者:飞龙

协议:CC BY-NC-SA 4.0

第七章:类、结构和枚举

在上一章中,你学习了如何使用函数和闭包来分组指令序列。

是时候考虑如何在代码中表示复杂对象了。例如,考虑一辆车。你可以使用一个String常量来存储车名,以及一个Double变量来存储车价,但它们之间并没有关联。你已经看到你可以通过分组指令来创建函数和闭包。在本章中,你将学习如何使用结构将常量和变量组合成一个单一实体,以及如何操作它们。你还将学习如何使用枚举来分组一组相关值。

到本章结束时,你将学会如何创建和初始化一个类,从现有类创建子类,创建和初始化一个结构,区分类和结构,以及创建枚举。

本章将涵盖以下主题:

  • 理解类

  • 理解结构

  • 理解枚举

技术要求

本章的 Xcode 游乐场位于本书代码包的Chapter07文件夹中,可以在此处下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Eighth-Edition

查看以下视频,看看代码的实际效果:

youtu.be/Yl9UuzSR_oE

如果你希望从头开始,创建一个新的游乐场,并将其命名为ClassesStructuresAndEnumerations。你可以一边阅读一边输入并运行本章中的所有代码。让我们从学习什么是类以及如何声明和定义它开始。

理解类

类对于表示复杂对象非常有用,例如:

  • 公司的个别员工信息

  • 在电子商务网站上出售的物品

  • 你家中用于保险目的的物品

下面是一个类声明和定义的例子:

class ClassName {
  property1
  property2
  property3
  method1() {
    code
  }
  method2() {
    code
  }
} 

每个类都有一个描述性的名称,它包含用于表示对象的变量或常量。与类关联的变量或常量称为属性

类还可以包含执行特定任务的函数。与类关联的函数称为方法

一旦你声明并定义了一个类,你就可以创建该类的实例。想象一下,你正在为动物园创建一个应用程序。如果你有一个Animal类,你可以使用该类的实例来表示动物园中的不同动物。这些实例的每个属性都将有不同的值。

要了解更多关于类的信息,请访问docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures

让我们看看如何使用类。你将学习如何声明和定义类,根据类声明创建实例,并操作这些实例。你将从下一节开始创建一个表示动物的类。

创建类声明

让我们声明并定义一个可以存储关于动物详细信息的类。将以下代码添加到你的游乐场中:

class Animal {
  var name: String = ""
  var sound: String = ""
  var numberOfLegs: Int = 0
  var breathesOxygen: Bool = true
  func makeSound() {
    print(sound)
  }
} 

你刚刚声明了一个非常简单的名为 Animal 的类。惯例规定,类名应以大写字母开头。这个类有属性来存储动物的名字、它发出的声音、它有多少条腿以及它是否呼吸氧气。这个类还有一个名为 makeSound() 的方法,它会将产生的噪音打印到调试区域。

现在你有了 Animal 类,让我们在下一节中使用它来创建一个表示动物的实例。

创建类的实例

一旦你声明并定义了一个类,你就可以创建该类的实例。现在,你将创建一个表示猫的 Animal 类的实例。按照以下步骤操作:

  1. 要创建 Animal 类的实例,列出所有属性并调用它的 makeSound() 方法;在你的类声明之后输入以下代码并运行:

    let cat = Animal()
    print(cat.name)
    print(cat.sound)
    print(cat.numberOfLegs)
    print(cat.breathesOxygen)
    cat.makeSound() 
    

你可以通过在实例名称后键入一个点,然后跟随着你想要的属性或方法来访问实例属性和方法。你将在调试区域看到实例属性和方法的值。由于这些值是在创建类时分配的默认值,因此 namesound 包含空字符串,numberOfLegs 包含 0breathesOxygen 包含 true,而 makeSound() 方法打印一个空字符串。

  1. 让我们给这个实例的属性分配一些值。按照以下所示修改你的代码:

    let cat = Animal()
    **cat****.****name****=****"Cat"**
    **cat****.****sound****=****"Mew"**
    **cat****.****numberOfLegs****=****4**
    **cat****.****breathesOxygen****=****true**
    print(cat.name) 
    

现在,当你运行程序时,以下内容将在调试区域显示:

Cat
Mew
4
true
Mew 

所有实例属性值和 makeSound() 方法的输出都将打印到调试区域。

注意,在这里,你首先创建实例,然后分配值给该实例。也可以在创建实例时分配值,这可以通过在类声明中实现一个 初始化器 来完成。

  1. 初始化器负责确保在创建类时所有实例属性都有有效的值。让我们为 Animal 类添加一个初始化器。按照以下所示修改你的类定义:

    class Animal {
      var name: String
      var sound: String
      var numberOfLegs: Int
      var breathesOxygen: Bool
    **init****(****name****:** **String****,** **sound****:** **String****,** **numberOfLegs****:**
    **Int****,** **breathesOxygen****:** **Bool****) {**
    **self****.****name****=** **name**
    **self****.****sound****=** **sound**
    **self****.****numberOfLegs****=** **numberOfLegs**
    **self****.****breathesOxygen****=** **breathesOxygen**
     **}**
      func makeSound() {
        print(sound)
      }
    } 
    

如你所见,初始化器使用了 init 关键字,并有一个参数列表,这些参数将用于设置属性值。请注意,self 关键字区分了属性名和参数。例如,self.name 指的是属性,而 name 指的是参数。

在初始化过程结束时,类中的每个属性都应该有一个有效的值。

  1. 在这个阶段,你会在代码中看到一些错误,因为函数调用没有参数。你需要更新你的函数调用以解决这个问题。按照以下所示修改你的代码并运行:

     func makeSound() {
        print(sound)
      }
    }
    let cat = Animal(**name****:** **"Cat"****,** **sound****:** **"Mew"****,** **numberOfLegs****:** **4****,** **breathesOxygen****:** **true**)
    print(cat.name) 
    

结果与步骤 2中的相同,但你在一个指令中创建了实例并设置了其属性。太棒了!

现在有不同类型的动物,如哺乳动物、鸟类、爬行动物和鱼类。你可以为每种类型创建一个类,但你也可以基于现有类创建一个子类。让我们在下一节中看看如何做到这一点。

创建子类

一个类的子类继承了一个现有类的所有方法和属性。如果你愿意,你还可以向其中添加额外的属性和方法。例如,对于一家 IT 公司,你可以有CustomerSupportAgent作为Employee类的子类。这个类将具有Employee类的所有属性,以及客户支持角色所需的额外属性。

现在,你将创建Mammal,它是Animal类的子类。按照以下步骤进行:

  1. 要声明Mammal类,在Animal类声明之后键入以下代码:

    class Mammal: Animal {
      let hasFurOrHair: Bool = true
    } 
    

在类名后面键入: Animal会使Mammal类成为Animal类的子类。它具有在Animal类中声明的所有属性和方法,以及一个额外的属性:hasFurOrHair。由于Animal类是Mammal类的父类,你可以将其称为Mammal类的超类。

  1. 按照以下所示修改创建你的类实例的代码,然后运行它:

    let cat = **Mammal**(name: "Cat", sound: "Mew", numberOfLegs: 4, breathesOxygen: true) 
    

cat现在是一个Mammal类的实例,而不是Animal类的实例。正如你所看到的,调试区域显示的结果与之前相同,没有错误。不过,hasFurOrHair的值尚未显示。让我们修复这个问题。

  1. 在你的游乐场中的所有其他代码之后键入以下代码以显示hasFurOrHair属性的值并运行它:

    print(cat.hasFurOrHair) 
    

由于Animal类的初始化器没有参数来分配hasFurOrHair的值,将使用默认值,调试区域将显示true

你已经看到子类可以具有额外的属性。子类还可以具有额外的属性和方法,子类中的方法实现可以与超类实现不同。让我们在下一节中看看如何做到这一点。

覆盖超类方法

到目前为止,你一直在使用多个print()语句来显示类实例的值。你将实现一个description()方法来在调试区域显示所有实例属性,因此不再需要多个print()语句。按照以下步骤进行:

  1. 按照以下所示修改你的Animal类声明以实现description()方法:

    class Animal {
      var name: String
      var sound: String
      var numberOfLegs: Int
      var breathesOxygen: Bool = true
      init(name: String, sound: String, numberOfLegs:
      Int, breathesOxygen: Bool) {
        self.name = name
        self.sound = sound
        self.numberOfLegs = numberOfLegs
        self.breathesOxygen = breathesOxygen
      }
      func makeSound() {
        print(sound)
      }
    **func****description****() ->** **String** **{**
    **"****name:** **\(****name****)****sound:** **\(****sound****)**
     **numberOfLegs:** **\(****numberOfLegs****)**
     **breathesOxygen:** **\(****breathesOxygen****)****"**
     **}**
    } 
    
  2. 按照以下所示修改你的代码,用description()方法代替多个print()语句,然后运行程序:

    let cat = Mammal(name: "Cat", sound: "Mew",
    numberOfLegs: 4, breathesOxygen: true)
    **print(****cat****.****description****())**
    cat.makeSound() 
    

你将在调试区域看到以下内容:

name: Cat sound: Mew numberOfLegs: 4 breathesOxygen: true
Mew 

如您所见,尽管description()方法在Mammal类中没有实现,但它却在Animal类中实现了。这意味着它将被Mammal类继承,并且实例属性将被打印到调试区域。请注意,hasFurOrHair属性的值缺失,并且您不能将其放入description()方法中,因为Animal类中没有hasFurOrHair属性。

  1. 您可以更改Mammal类中description()方法的实现,以显示hasFurOrHair属性的值。将以下代码添加到您的Mammal类定义中并运行它:

    class Mammal: Animal {
      let hasFurOrHair: Bool = true
    **override****func****description****() ->** **String** **{**
    **super****.****description****()** **+****" hasFurOrHair:**
    **\(****hasFurOrHair****)"**
     **}**
    } 
    

override关键字在这里用于指定实现的description()方法将替换超类实现。super关键字用于调用超类的description()实现。然后,hasFurOrHair的值被添加到super.description()返回的字符串中。

您将在调试区域看到以下内容:

name: Cat sound: Mew numberOfLegs: 4 breathesOxygen: true hasFurOrHair: true
Mew 

hasFurOrHair属性的值在调试区域中显示,表明您正在使用Mammal子类的description()方法实现。

您已经创建了类和子类声明,并为两者创建了实例。您还为两者添加了初始化器和方法。太酷了!让我们在下一节中看看如何声明和使用结构体。

理解结构体

与类一样,结构体也组合了用于表示对象和执行特定任务的属性和方法。还记得您创建的Animal类吗?您也可以使用结构体来完成相同的事情。不过,类和结构体之间还是有区别的,您将在本章后面了解更多。

下面是结构体声明和定义的示例:

struct StructName {
  property1
  property2
  property3
  method1() {
    code
  }
  method2(){
    code
  }
} 

如您所见,结构体与类非常相似。它也有一个描述性的名称,可以包含属性和方法。您也可以创建结构体的实例。

想要了解更多关于结构体的信息,请访问docs.swift.org/swift-book/documentation/the-swift-programming-language/classesandstructures

让我们看看如何与结构体一起工作。您将学习如何声明和定义结构体,基于结构体创建实例,并对其进行操作。您将在下一节中创建一个结构体来表示爬行动物。

创建结构体声明

继续动物主题,让我们声明并定义一个可以存储关于爬行动物详细信息的结构体。在您的游乐场中所有其他代码之后添加以下代码:

struct Reptile {
  var name: String
  var sound: String
  var numberOfLegs: Int
  var breathesOxygen: Bool
  let hasFurOrHair: Bool = false
  func makeSound() {
    print(sound)
  }
  func description() -> String {
    "Structure: Reptile name: \(name)
    sound: \(sound)
    numberOfLegs: \(numberOfLegs)
    breathesOxygen: \(breathesOxygen)
    hasFurOrHair: \(hasFurOrHair)"
  }
} 

如你所见,这几乎与你之前所做的Animal类声明相同。结构体的名称也应该以大写字母开头,并且这个结构体有属性来存储动物的名字、它发出的声音、它有多少条腿、它是否呼吸氧气以及它是否有毛皮或毛发。这个结构体还有一个makeSound()方法,它会将发出的声音打印到调试区域。

现在你有了Reptile结构体的声明,让我们在下一节中使用它来创建一个表示蛇的实例。

创建结构体的实例

与类一样,你可以从结构体声明中创建实例。现在,你将创建一个表示蛇的Reptile结构体的实例,打印出该实例的属性值,并调用makeSound()方法。在你的游乐场中的所有其他代码之后输入以下内容并运行它:

var snake = Reptile(name: "Snake", sound: "Hiss",
numberOfLegs: 0, breathesOxygen: true)
print(snake.description())
snake.makeSound() 

注意,你不需要实现一个初始化器;结构体自动为其所有属性获得一个名为成员初始化器的初始化器。真方便!以下内容将在调试区域显示:

Structure: Reptile name: Snake sound: Hiss numberOfLegs: 0 breathesOxygen: true hasFurOrHair: false
Hiss 

尽管结构体声明与类声明非常相似,但类和结构体之间有两个区别:

  • 结构体不能从另一个结构体继承

  • 类是引用类型,而结构体是值类型

让我们看看下一节中值类型和引用类型之间的区别。

比较值类型和引用类型

类是引用类型。这意味着当你将类实例赋值给变量时,你是在变量中存储原始实例的内存位置,而不是实例本身。

结构体是值类型。这意味着当你将结构体实例赋值给变量时,该实例被复制,你对原始实例所做的任何更改都不会影响副本。

现在,你将创建一个类的实例和一个结构体的实例,并观察它们之间的差异。按照以下步骤操作:

  1. 你将首先创建一个包含结构体实例的变量,并将其赋值给第二个变量,然后更改第二个变量中属性的值。输入以下代码并运行它:

    struct SampleValueType {
      var sampleProperty = 10
    }
    var a = SampleValueType()
    var b = a
    b.sampleProperty = 20
    print(a.sampleProperty)
    print(b.sampleProperty) 
    

在这个例子中,你声明了一个包含一个属性sampleProperty的结构体SampleValueType。然后,你创建了该结构体的一个实例并将其赋值给变量a。之后,你将a赋值给一个新的变量b。然后,你将bsampleProperty值更改为20

当你打印出asampleProperty值时,调试区域将显示10,这表明对bsampleProperty值的任何更改都不会影响asampleProperty值。这是因为当你将a赋值给b时,a的一个副本被赋值给b,因此它们是独立的实例,不会相互影响。

  1. 接下来,你将创建一个包含类实例的变量,并将其分配给第二个变量,然后更改第二个变量的属性值。输入以下代码并运行它:

    class SampleReferenceType {
      var sampleProperty = 10
    }
    var c = SampleReferenceType()
    var d = c
    c.sampleProperty = 20
    print(c.sampleProperty)
    print(d.sampleProperty) 
    

在这个例子中,你声明了一个包含一个属性 sampleProperty 的类 SampleReferenceType。然后,你创建了该类的实例并将其分配给一个变量 c。之后,你将 c 分配给一个新的变量 d。接下来,你将 dsampleProperty 值更改为 20

当你打印出 csampleProperty 值时,在调试区域中会打印出 20,这表明对 cd 的任何更改都会影响相同的 SampleReferenceType 实例。

现在,问题是,你应该使用类还是结构体?让我们在下一节中探讨这个问题。

在类和结构体之间做出选择

你已经看到你可以使用类或结构体来表示复杂对象。那么,你应该使用哪一个呢?

除非你需要类才能实现某些功能,例如子类,否则建议使用结构体。这有助于防止由于类是引用类型而可能发生的某些微妙错误。

太棒了!现在你已经了解了类和结构体,让我们看看枚举,它允许你将相关值分组,在下一节中。

理解枚举

枚举允许你将相关值分组,例如以下内容:

  • 指南针方向

  • 交通信号灯颜色

  • 彩虹的颜色

为了理解为什么枚举对于这个目的来说非常理想,让我们考虑以下示例。

想象你正在编写交通信号灯的代码。你可以使用一个整型变量来表示不同的交通信号灯颜色,其中 0 代表红色,1 代表黄色,2 代表绿色,如下所示:

var trafficLightColor = 2 

虽然这是一种表示交通信号灯的方法,但当将 3 分配给 trafficLightColor 时会发生什么?这是一个问题,因为 3 并不代表有效的交通信号灯颜色。因此,如果我们能将 trafficLightColor 的可能值限制为它可以显示的颜色,那就更好了。

下面是一个枚举声明和定义的例子:

enum EnumName {
  case value1
  case value2
  case value3
} 

每个枚举都有一个描述性名称,其主体包含该枚举的关联值。

要了解更多关于枚举的信息,请访问 docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations

让我们看看如何使用枚举。你将学习如何创建和操作它们。你将从下一节创建一个表示交通信号灯颜色的枚举开始。

创建一个枚举

让我们创建一个枚举来表示交通信号灯。按照以下步骤操作:

  1. 将以下代码添加到您的游乐场中并运行它:

    enum TrafficLightColor {
      case red
      case yellow
      case green
    }
    var trafficLightColor = TrafficLightColor.red 
    

这创建了一个名为 TrafficLightColor 的枚举,它将红色、黄色和绿色值分组在一起。trafficLightColor 变量的值限制为 redyellowgreen;设置任何其他值将生成错误。

  1. 就像类和结构体一样,枚举也可以包含方法。让我们给 TrafficLightColor 添加一个方法。按照以下所示修改你的代码,使 TrafficLightColor 返回一个表示交通灯颜色的字符串,并运行它:

    enum TrafficLightColor {
      case red
      case yellow
      case green
    **func****description****() ->** **String** **{**
    **switch****self** **{**
    **case****.red****:**
    **"red"**
    **case****.yellow****:**
    **"yellow"**
     **case .green:**
     **"green"**
     **}**
     **}**
    }
    var trafficLightColor = TrafficLightColor.red
    **print(****trafficLightColor****.****description****())** 
    

description() 方法返回一个字符串,取决于 trafficLightColor 的值。由于 trafficLightColor 的值为 TrafficLightColor.red红色将出现在调试区域。

你已经学会了如何创建和使用枚举来存储分组值,以及如何向它们添加方法。做得好!

摘要

在本章中,你学习了如何使用类声明复杂对象,如何创建类的实例,如何创建子类,以及如何重写类方法。你还学习了如何声明结构体,创建结构体的实例,以及理解引用类型和值类型之间的区别。最后,你学习了如何使用枚举来表示一组特定的值。

现在,你知道了如何使用类和结构体来表示复杂对象,以及如何在你的程序中使用枚举将相关值分组在一起。

在下一章中,你将学习如何使用协议在类和结构体中指定常见特性,如何使用扩展扩展内置类的功能,以及如何在程序中处理错误。

加入我们的 Discord 社区!

与其他用户、专家和作者本人一起阅读这本书。提问,为其他读者提供解决方案,通过 Ask Me Anything 会话与作者聊天,等等。扫描二维码或访问链接加入社区。

packt.link/ios-Swift

https://packt.link/ios-Swift

第八章:协议、扩展和错误处理

在上一章中,你学习了如何使用类或结构体来表示复杂对象,以及如何使用枚举将相关值分组在一起。

在本章中,你将了解协议扩展错误处理。协议定义了一个方法、属性和其他要求的蓝图,这些可以由类、结构体或枚举采用。扩展使你能够为现有的类、结构体或枚举提供新功能。错误处理涵盖了如何响应和从程序中的错误中恢复。

到本章结束时,你将能够编写自己的协议以满足你应用程序的需求,使用扩展为现有类型添加新功能,并在你的应用程序中处理错误条件而不会崩溃。

本章将涵盖以下主题:

  • 探索协议

  • 探索扩展

  • 探索错误处理

技术要求

本章的 Xcode 游乐场位于本书代码包的Chapter08文件夹中,可以在此处下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Ninth-Edition

查看以下视频,看看代码的实际应用:

youtu.be/fV6VNlDyyG0

如果你希望从头开始,创建一个新的游乐场,并将其命名为ProtocolsExtensionsAndErrorHandling。你可以一边输入一边运行本章中的所有代码。让我们从协议开始,这是一种指定类、结构体或枚举应该具有的属性和方法的方式。

探索协议

协议就像蓝图,决定了对象必须拥有的属性或方法。在你声明了一个协议之后,类、结构体和枚举可以采用它,并为所需的属性和方法提供自己的实现。

这就是协议声明的样子:

protocol ProtocolName {
  var readWriteProperty1 {get set}
  var readOnlyProperty2 {get}
  func methodName1()
  func methodName2()
} 

就像类和结构体一样,协议名称以大写字母开头。属性使用var关键字声明。如果你想有一个可读可写的属性,你使用{get set},如果你想有一个只读属性,你使用{get}。请注意,你只需指定属性和方法名称;实现是在采用类、结构体或枚举内部完成的。

更多关于协议的信息,请访问docs.swift.org/swift-book/documentation/the-swift-programming-language/protocols

为了帮助你理解协议,想象一个快餐店使用的应用程序。管理层已经决定显示正在提供的餐点的卡路里含量。该应用程序目前有以下类、结构体和枚举,它们都没有实现卡路里含量:

  • 一个Burger

  • 一个Fries结构体

  • 一个Sauce枚举

将以下代码添加到您的游乐场中,以声明Burger类、Fries结构和Sauce枚举:

class Burger {
}
struct Fries {
}
enum Sauce {
  case chili
  case tomato
} 

这些代表应用中现有的类、结构和枚举。不用担心空定义,因为它们对于本课程不是必需的。如您所见,它们目前都没有卡路里计数。让我们学习如何创建一个指定实现卡路里计数所需属性和方法协议。您将在下一节中声明此协议。

创建协议声明

让我们创建一个协议,该协议指定一个必需的属性calories和一个方法description()。在类、结构和枚举声明之前,将以下内容输入到您的游乐场中:

protocol CalorieCountable {
  var calories: Int { get }
  func description() -> String
} 

此协议命名为CalorieCountable。它指定了采用它的任何对象必须有一个属性calories,该属性包含卡路里计数,以及一个返回字符串的方法description(){ get }表示您只需能够读取存储在calories中的值,而无需写入它。请注意,description()方法的定义没有指定,因为这将在类、结构或枚举中完成。您要采用协议只需在类名后键入一个冒号,然后是协议名,并实现所需的属性和方法。

要使Burger类符合此协议,请按以下方式修改您的代码:

class Burger**:** **CalorieCountable** {
**let****calories****=****800**
**func****description****() ->** **String** **{**
**"This burger has** **\(****calories****)** **calories"**
 **}**
} 

如您所见,calories属性和description()方法已添加到Burger类中。尽管协议指定了一个变量,但您在这里可以使用一个常量,因为协议只要求您获取calories的值,而不需要设置它。

让我们让Fries结构也采用此协议。按以下方式修改您的Fries结构代码:

struct Fries**:** **CalorieCountable**{
**let****calories****=****500**
**func****description****() ->** **String** **{**
**"****These fries have** **\(****calories****)** **calories"**
 **}**
} 

添加到Fries结构的代码与添加到Burger类的代码类似,并且它现在也符合CalorieCountable协议。

您也可以以相同的方式修改Sauce枚举,但让我们使用扩展来实现。扩展可以扩展现有类、结构或枚举的功能。您将在下一节中使用扩展将CalorieCountable协议添加到Sauce枚举中。

探索扩展

扩展允许您在不修改原始对象定义的情况下向对象提供额外的功能。您可以在 Apple 提供的对象上使用它们(您无法访问对象定义的地方)或当您希望将代码分离以提高可读性和易于维护时。以下是一个扩展的示例:

class ExistingType {
  property1
  method1()
}
extension ExistingType : ProtocolName {
  property2
  method2()
} 

在这里,扩展被用来向现有类提供额外的属性和方法。

更多关于扩展的信息,请访问docs.swift.org/swift-book/documentation/the-swift-programming-language/extensions

让我们看看如何使用扩展。您将首先通过在下一节中使用扩展使 Sauce 枚举符合 CalorieCountable 协议。

通过扩展采用协议

目前,Sauce 枚举不符合 CalorieCountable 协议。您将使用扩展来添加使其符合所需的属性和方法。在 Sauce 枚举声明之后输入以下代码:

enum Sauce {
  case chili
  case tomato
}
**extension****Sauce****:** **CalorieCountable** **{**
**var****calories****:** **Int** **{**
**switch****self** **{**
**case** **.****chili****:**
**20**
**case** **.****tomato****:**
**15**
 **}**
 **}**
**func****description****() ->** **String** **{**
**"This sauce has** **\(****calories****)** **calories"**
 **}**
**}** 

如您所见,对 Sauce 枚举的原定义没有进行任何更改。如果您想扩展现有的 Swift 标准类型的功能,如 StringInt,这也很有用。

枚举实例不能像结构体和类那样在属性中存储值,因此使用 switch 语句根据枚举的值返回卡路里数。description() 方法与 Burger 类和 Fries 结构体中的相同。

所有三个对象都有一个 calories 属性和一个 description() 方法。太棒了!

让我们看看如何在下一节中将它们放入数组中,并执行操作以获取餐点的总卡路里数。

创建不同类型对象的数组

通常,数组的元素必须是同一类型。然而,由于 Burger 类、Fries 结构体和 Sauce 枚举都符合 CalorieCountable 协议,您可以创建一个包含符合此协议的元素的数组。按照以下步骤操作:

  1. 要将 Burger 类的实例、Fries 结构体和 Sauce 枚举的实例添加到数组中,在文件中的所有其他代码之后输入以下代码:

    let burger = Burger()
    let fries = Fries()
    let sauce = Sauce.tomato
    let foodArray: [CalorieCountable] = [burger, fries, sauce] 
    
  2. 要获取总卡路里数,在创建 foodArray 常量之后的行中添加以下代码:

    let totalCalories = foodArray.reduce(0, {$0 + $1.calories})
    print(totalCalories) 
    

reduce 方法用于从 foodArray 数组的元素中生成一个单一值。此方法的第一参数是初始值,设置为 0。第二个参数是一个闭包,它将初始值与一个元素的 calories 属性中存储的值组合。这将对 foodArray 数组中的每个元素重复进行,并将结果分配给 totalCalories。总数量,1315,将在调试区域显示。

您已经学习了如何创建一个协议,并使类、结构体或枚举符合它,无论是通过类定义还是通过扩展。让我们接下来看看错误处理,并了解如何在程序中响应或恢复错误。

探索错误处理

当您编写应用程序时,请记住可能会发生错误条件,错误处理是您的应用程序如何响应和从这些条件中恢复的方式。

首先,你创建一个符合 Swift 的Error协议的类型,这使得该类型可用于错误处理。枚举通常被使用,因为你可以为不同类型的错误指定关联值。当发生意外情况时,你可以通过抛出错误来停止程序执行。你使用throw语句来完成此操作,并提供一个符合Error协议的类型的实例,并带有适当的值。这允许你看到出了什么问题。

当然,如果你能在不停止程序的情况下响应错误,那就更好了。为此,你可以使用do-catch块,它看起来像这样:

do {
  try expression1
  statement1
} catch {
  statement2
} 

在这里,你尝试使用try关键字在do块中执行代码。如果抛出错误,catch块中的语句将被执行。你可以有多个catch块来处理不同类型的错误。

更多关于错误处理的信息,请访问docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling

例如,假设你有一个需要访问网页的应用程序。然而,如果该网页所在的服务器宕机,你需要编写代码来处理错误,例如尝试使用备用网页服务器或通知用户服务器已宕机。

让我们创建一个符合Error协议的枚举,当发生错误时使用throw语句停止程序执行,并使用do-catch块来处理错误。按照以下步骤操作:

  1. 将以下代码输入到你的 playground 中:

    enum WebsiteError: Error {
      case noInternetConnection
      case siteDown
      case wrongURL
    } 
    

这声明了一个符合Error协议的枚举WebsiteError,它涵盖了三种可能的错误条件:没有互联网连接、网站宕机或 URL 无法解析。

  1. WebsiteError定义之后输入以下代码以声明一个函数,该函数在WebpageError声明之后检查网站是否可用:

    func checkWebsite(siteUp: Bool) throws -> String {
      if !siteUp {
        throw WebsiteError.siteDown
      }
      return "Site is up"
    } 
    

如果siteUptrue,则返回"Site is up"。如果siteUpfalse,程序将停止执行并抛出错误。

  1. checkWebsite(siteUp:)函数定义之后输入以下代码并运行你的程序:

    let siteStatus = true
    try checkWebsite(siteUp: siteStatus) 
    

由于siteStatustrueSite is up将出现在结果区域。

  1. siteStatus的值更改为false并运行你的程序。你的程序会崩溃,并在调试区域显示以下错误消息:

    Playground execution terminated: An error was thrown and was not caught:
    error: error: 
    
  2. 当然,如果你能在不使程序崩溃的情况下处理错误,那就更好了。你可以通过使用do-catch块来实现这一点。按照以下所示修改你的代码并运行它:

    let siteStatus = false
    **do** **{**
     **print(**try checkWebsite(siteUp: siteStatus)**)**
    **}** **catch** **{**
    **print(****error****)**
    **}** 
    

do块尝试执行checkWebsite(siteUp:)函数,并在成功时打印状态。如果有错误发生,而不是崩溃,catch块中的语句将被执行,错误消息siteDown将出现在调试区域。

你可以通过实现多个catch块来让你的程序处理不同的错误条件。有关详细信息,请参阅此链接:docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling

你已经学会了如何在应用程序中处理错误而不会导致其崩溃。太棒了!

摘要

在本章中,你学习了如何编写协议以及如何使类、结构和枚举符合这些协议。你还学习了如何通过使用扩展来扩展类的能力。最后,你学习了如何使用do-catch块来处理错误。

这些内容现在可能看起来相当抽象且难以理解,但在本书的第三部分中,你将看到如何使用协议在程序的各个部分实现常见功能,而不是一遍又一遍地编写相同的程序。你将看到扩展在组织代码方面的有用性,这使得代码易于维护。最后,你将看到良好的错误处理如何使定位你在编写应用程序时犯的错误变得容易。

在下一章中,你将了解Swift 并发,这是在 Swift 中处理异步操作的新方法。

加入我们的 Discord 频道!

与其他用户、专家以及作者本人一起阅读这本书。提出问题,为其他读者提供解决方案,通过“问我任何问题”的环节与作者聊天,还有更多。扫描二维码或访问链接加入社区。

packt.link/ios-Swift

https://packt.link/ios-Swift

第九章:Swift 并发

在 WWDC21 上,Apple 引入了 Swift 并发,它为 Swift 5.5 添加了对结构化异步和并行编程的支持。它允许您编写更易读、更易于理解的并发代码。在 WWDC24 上,Apple 引入了 Swift 6,它通过在编译时诊断 数据竞争 来简化并发编程。

目前,不建议为大型现有项目启用严格并发,因为它可能会生成多个错误和警告。然而,鉴于这是 Apple 的未来方向,您将在本章和本书的 第三部分 中为项目启用它,以便您可以学习和获得相关经验。

在本章中,您将学习 Swift 并发的基本概念。接下来,您将检查一个没有并发的应用程序,并探讨其问题。然后,您将使用 async/await 在应用程序中实现并发。最后,您将通过使用 async-let 使您的应用程序更加高效。

到本章结束时,您将了解 Swift 并发的工作原理以及如何更新您自己的应用程序以使用它。

以下内容将涵盖:

  • 理解 Swift 并发

  • 检查没有并发功能的 app

  • 使用 async/await 更新应用程序

  • 使用 async-let 提高效率

技术要求

我们将使用一个示例应用程序,BreakfastMaker,来理解 Swift 并发的概念。

本章完成的 Xcode 项目位于本书代码包的 Chapter09 文件夹中,您可以通过以下链接下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Ninth-Edition

查看以下视频以查看代码的实际运行情况:

youtu.be/uEckcWHFeiE

让我们从学习下一节中的 Swift 并发开始。

理解 Swift 并发

在 Swift 5.5 中,Apple 添加了对以结构化方式编写 异步并行 代码的支持。

异步代码允许您的应用程序暂停和恢复代码。并行代码允许您的应用程序同时运行多个代码片段。这使得您的应用程序能够在执行如从互联网下载数据等操作的同时更新用户界面。

您可以在 WWDC21 期间找到所有 Apple 的 Swift 并发视频链接,请访问 developer.apple.com/news/?id=2o3euotz

您可以在 developer.apple.com/news/?id=2o3euotz 阅读 Apple 的 Swift 并发文档。

在 WWDC24 上,Apple 发布了 Swift 6。使用 Swift 6 语言模式,编译器现在可以保证并发程序没有数据竞争。这意味着你的应用程序的一部分代码不能再访问另一部分代码正在修改的同一内存区域。然而,当你创建一个新的 Xcode 项目时,它默认使用 Swift 5 语言模式,你必须打开 Swift 6 语言模式才能启用此功能。

要查看 Apple 的 WWDC24 视频关于将你的应用程序迁移到 Swift 6,请点击此链接:developer.apple.com/videos/play/wwdc2024/10169/

要查看 Apple 关于将你的应用程序迁移到 Swift 6 的文档,请点击此链接:www.swift.org/migration/documentation/migrationguide/

为了让你了解 Swift 并发的工作方式,想象一下你正在为早餐做水煮蛋和烤面包。这里有一种做法:

  1. 将两片面包放入烤面包机中。

  2. 等待两分钟,直到面包烤熟。

  3. 在平底锅中放入两个鸡蛋,并盖上锅盖。

  4. 等待七分钟,直到鸡蛋煮熟。

  5. 上菜并享用早餐。

总共需要九分钟。现在,思考一下这个事件序列。你只是盯着烤面包机和平底锅发呆吗?你可能会在面包在烤面包机中,鸡蛋在平底锅中时使用手机。换句话说,你可以在准备烤面包和鸡蛋的同时做其他事情。因此,事件序列更准确地描述如下:

  1. 将两片面包放入烤面包机中。

  2. 用你的手机计时两分钟,直到面包烤熟。

  3. 在一个装有沸水的大平底锅里放两个鸡蛋,并盖上锅盖。

  4. 用你的手机计时七分钟,直到鸡蛋煮熟。

  5. 上菜并享用早餐。

在这里,你可以看到你与烤面包机和平底锅的交互可以被暂停,然后恢复,这意味着这些操作是异步的。操作仍然需要九分钟,但你在这段时间里可以做其他事情。

还有一个需要考虑的因素。你不需要等到面包烤熟后再把鸡蛋放入平底锅。这意味着你可以修改步骤的顺序如下:

  1. 将两片面包放入烤面包机中。

  2. 当面包正在烤制时,将两个鸡蛋放入一个装有沸水的大平底锅中,并盖上锅盖。

  3. 用你的手机计时七分钟。在这段时间里,面包会烤熟,鸡蛋会煮熟。

  4. 上菜并享用早餐。

烤面包和煮鸡蛋现在是并行进行的,这可以为你节省两分钟。太棒了!然而,请注意,你还有更多的事情需要关注。

现在你已经理解了异步和并行操作的概念,让我们在下一节研究没有并发的应用程序存在的问题。

检查没有并发的应用程序

你已经看到了异步和并行操作如何帮助你更快地准备早餐,并允许你在做这件事的同时使用手机。现在,让我们看看一个模拟准备早餐过程的示例应用。最初,这个应用没有实现并发,这样你可以看到它对应用的影响。按照以下步骤操作:

  1. 如果你还没有这样做,请在此链接下载本书的代码包中的Chapter09文件夹:github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Eighth-Edition

  2. 打开Chapter09文件夹,你会看到两个文件夹,BreakfastMaker-startBreakfastMaker-complete。第一个文件夹包含你将在本章中修改的应用,第二个文件夹包含完成的应用。

  3. 打开BreakfastMaker-start文件夹,然后打开BreakfastMaker Xcode 项目。在项目导航器中点击Main故事板文件。你应该在视图控制器场景中看到四个标签和一个按钮,如图所示:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_01.png

图 9.1:主故事板文件显示视图控制器场景

应用将显示一个屏幕,显示吐司和鸡蛋的状态,以及上菜和上桌所需的时间。应用还将显示一个按钮,你可以使用它来测试用户界面的响应性。

如果你对其中一些概念不熟悉,不要担心。你将在下一章,即第十章设置用户界面中学习如何使用故事板为你的应用构建用户界面。

  1. 在项目导航器中点击ViewController文件。你应该在编辑器区域看到以下代码:

    import UIKit
    class ViewController: UIViewController {
       @IBOutlet var toastLabel: UILabel!
       @IBOutlet var eggLabel: UILabel!
       @IBOutlet var plateAndServeLabel: UILabel!
       @IBOutlet var elapsedTimeLabel: UILabel!
       override func viewDidAppear(_ animated: Bool) {
          super.viewDidAppear(animated)
          let startTime = Date().timeIntervalSince1970
          toastLabel.text = "Making toast..."
          toastLabel.text = makeToast()
          eggLabel.text = "Boiling eggs..."
          eggLabel.text = boilEggs()
          plateAndServeLabel.text = plateAndServe()
          let endTime = Date().timeIntervalSince1970
          elapsedTimeLabel.text = "Elapsed time is
          \(((endTime - startTime) * 100).rounded()
          / 100) seconds"
       }
       func makeToast() -> String {
          sleep(2)
          return "Toast done"
       }
       func boilEggs() -> String {
          sleep(7)
          return "Eggs done"
       }
       func plateAndServe() -> String {
          return "Plating and serving done"
       }
       @IBAction func testButton(_ sender: UIButton) {
          print("Button tapped")
       }
    } 
    

如你所见,这段代码模拟了之前章节中描述的制作早餐的过程。让我们来分解它:

@IBOutlet var toastLabel: UILabel!
@IBOutlet var eggLabel: UILabel!
@IBOutlet var plateAndServeLabel: UILabel!
@IBOutlet var elapsedTimeLabel: UILabel! 

这些输出连接到Main故事板文件中的四个标签。当你运行应用时,这些标签将显示吐司和鸡蛋的状态,上菜和上桌,以及完成过程所需的时间。

override  func viewDidAppear(_ animated: Bool) { 

这个方法在视图控制器的视图出现在屏幕上时被调用。

let startTime = Date().timeIntervalSince1970 

这个语句将 startTime 设置为当前时间,这样应用就可以稍后计算制作餐点所需的时间。

toastLabel.text = "Making toast..." 

这个语句使得 toastLabel 显示文本“制作吐司…”

toastLabel.text = makeToast() 

这个语句调用了 makeToast()方法,该方法等待两秒钟来模拟制作吐司所需的时间,然后返回文本“吐司完成”,该文本将由 toastLabel 显示。

eggLabel.text = "Boiling eggs..." 

这个语句使得 eggLabel 显示文本“正在煮鸡蛋…”

eggLabel.text = boilEggs() 

这个语句调用了 boilEggs()方法,该方法等待七秒钟来模拟煮两个鸡蛋所需的时间,然后返回文本“鸡蛋完成”,该文本将由 eggLabel 显示。

plateAndServeLabel.text = plateAndServe() 

这个语句调用了 plateAndServe()方法,该方法返回文本“上菜和上桌完成”,该文本将由plateAndServeLabel显示。

let endTime = Date().timeIntervalSince1970 

此语句将endTime设置为当前时间。

elapsedTimeLabel.text = "Elapsed time is
\(((endTime - startTime) * 100).rounded()
/ 100) seconds" 

该语句计算经过的时间(大约八秒),这将通过elapsedTimeLabel显示。

@IBAction func testButton(_ sender: UIButton) {
   print("Button tapped")
} 

此方法每次屏幕上的按钮被点击时,在调试区域显示按钮点击

构建并运行应用,并在用户界面出现时立即点击按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_02.png

图 9.2:iOS 模拟器运行 BreakfastMaker 应用,显示要点击的按钮

您应该注意到以下问题:

  • 初始时点击按钮没有效果,您只能在约九秒后在调试区域看到按钮点击

  • 制作吐司…煮鸡蛋… 从不显示,而吐司完成鸡蛋完成仅在约九秒后出现。

这种情况发生的原因是,当makeToast()boilEggs()方法运行时,您的应用代码没有更新用户界面。您的应用确实注册了按钮点击,但在makeToast()boilEggs()完成执行后,才能够处理这些点击并更新标签。这些问题不会为您的应用提供良好的用户体验。

您现在已经体验了没有实现并发的应用所呈现的问题。在下一节中,您将使用async/await修改应用,使其可以在makeToast()boilEggs()方法运行时更新用户界面。

使用 async/await 更新应用

如您之前所见,当makeToast()poachEgg()方法运行时,应用无响应。为了解决这个问题,您将在应用中使用 async/await。

在方法声明中写入async关键字表示该方法是非同步的。这看起来是这样的:

func methodName() async -> returnType { 

在方法调用前写入await关键字标记了一个可能挂起执行的点,从而允许其他操作运行。这看起来是这样的:

await methodName() 

您可以观看 Apple 的 WWDC21 视频,讨论 async/await,链接为developer.apple.com/videos/play/wwdc2021/10132/

您将修改您的应用以使用 async/await。这将使其能够挂起makeToast()poachEgg()方法以处理按钮点击,更新用户界面,然后之后继续执行这两个方法。您还将通过开启 Swift 6 语言模式来为您的应用启用严格的并发检查。请按照以下步骤操作:

  1. 在项目导航器中,点击顶部的BreakfastMaker图标,然后点击BreakfastMaker目标。在构建设置选项卡中,将Swift 语言版本更改为Swift 6

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_03.png

图 9.3:BreakfastMaker 项目,Swift 语言版本设置为 Swift 6

这使得对您的应用进行严格的并发检查成为可能。

  1. 在项目导航器中点击ViewController文件。修改makeToast()boilEggs()方法,如下所示,以使它们体内的代码异步:

    func makeToast() -> String {
       **try?****await****Task****.sleep(for: .seconds(2))**
       return "Toast done"
    }
    func boilEggs() -> String {
       **try?****await****Task****.sleep(for: .seconds(7))**
       return "Eggs done"
    } 
    

Task代表异步工作的一个单元。它有一个静态方法sleep(for:),该方法可以暂停执行指定的时间长度,以秒为单位。由于这个方法是一个抛出异常的方法,你将使用try?关键字来调用它,而不需要实现do-catch块。await关键字表示此代码可以被挂起,以允许其他代码运行。

使用try?将导致任何错误被抑制或忽略。在这种情况下这是可以接受的,因为睡眠 2 秒或 7 秒不太可能产生错误。在其他情况下,这可能不可接受,在这些情况下,do-catch块是一个更好的解决方案。你可能需要重新阅读第八章协议、扩展和错误处理,以获取有关如何实现do-catch块的信息。

  1. makeToast()boilEggs()都会出现错误。点击任一错误图标以显示错误信息:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_04.png

图 9.4:点击错误图标时的错误信息

错误显示出来是因为你在不支持并发的方法中调用异步方法。你需要将async关键字添加到方法声明中,以表明它是异步的。

  1. 对于每个方法,点击修复按钮以将async关键字添加到方法声明中。

  2. 确认你完成后的代码如下所示:

    func makeToast() **async** -> String {
       try? await Task.sleep(for: .seconds(2))
       return "Toast done"
    }
    func boilEggs() **async** -> String {
       try? await Task.sleep(for: .seconds(2))
       return "Eggs done"
    } 
    
  3. makeToast()poachEgg()方法中的错误应该已经消失,但在viewDidAppear()方法中会出现新的错误。点击其中一个错误图标以查看错误信息,该信息将与你在步骤 2中看到的相同。这是因为你在不支持并发的方法中调用异步方法。

  4. 点击修复按钮,将出现更多错误。

  5. 目前忽略方法声明中的错误,并点击makeToast()方法调用旁边的错误以查看错误信息:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_05.png

图 9.5:点击错误图标时的错误信息

这个错误信息显示出来是因为你在调用异步函数时没有使用await

  1. 点击修复按钮以在方法调用之前插入await关键字。

  2. 对于boilEggs()方法调用旁边的错误,重复步骤 7步骤 8await关键字也将被插入到boilEggs()方法调用中。

  3. 点击viewDidAppear()方法声明中的错误图标以查看错误信息:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_06.png

图 9.6:错误图标被突出显示的错误

这个错误显示出来是因为你不能使用async关键字使viewDidAppear()方法异步,因为这个功能在超类中不存在。

  1. 要解决这个问题,你需要移除async关键字,并将super.viewDidAppear()之后的全部代码放在一个Task块中,这将允许它在同步方法中异步执行。按照以下方式修改你的代码:

    override  func viewDidAppear(_ animated: Bool) {
       super.viewDidAppear(animated)
       **Task** **{**
          let startTime = Date().timeIntervalSince1970
          toastLabel.text = "Making toast..."
          toastLabel.text = await makeToast()
          eggLabel.text = "Boiling eggs..."
          eggLabel.text = await boilEggs()
          plateAndServeLabel.text = plateAndServe()
          let endTime = Date().timeIntervalSince1970
          elapsedTimeLabel.text = "Elapsed time is
          \(((endTime - startTime) * 100).rounded()
          / 100) seconds"
       **}**
    } 
    

构建并运行应用,一旦看到用户界面就立即点击按钮。注意,按钮已点击现在会立即显示在调试区域,标签也会相应更新。这是因为应用现在能够挂起makeToast()boilEggs()方法以响应用户点击,更新用户界面,并在稍后恢复方法执行。太棒了!

然而,如果你查看经过的时间,你会发现应用准备早餐的时间比之前稍微长了一些:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_07.png

图 9.7:iOS 模拟器运行 BreakfastMaker 应用,显示经过的时间

这部分原因是异步/等待挂起和恢复方法所需的额外处理,但还有一个因素在起作用。尽管makeToast()boilEggs()方法现在是异步的,但boilEggs()方法只有在makeToast()方法执行完毕后才开始执行。在下一节中,你将看到如何使用async-let来并行运行makeToast()boilEggs()方法。

使用 async-let 提高效率

尽管你的应用现在对按钮点击有响应,并且可以在makeToast()boilEggs()方法运行时更新用户界面,但这两个方法仍然按顺序执行。这里的解决方案是使用async-let

在定义常量时在let语句前写上async,然后在访问常量时写上await,允许异步方法的并行执行,如下所示:

async let temporaryConstant1 = methodName1()
async let temporaryConstant2 = methodName2()
await variable1 = temporaryConstant1
await variable2 = temporaryConstant1 

在这个例子中,methodName1()methodName2()将并行运行。

你将修改你的应用以使用async-let来使makeToast()poachEgg()方法并行运行。在ViewController文件中,按照以下方式修改Task块中的代码:

Task {
   let startTime = Date().timeIntervalSince1970
   toastLabel.text = "Making toast..."
**async****let** **tempToast** **=****makeToast****()**
   eggLabel.text = "Boiling eggs..."
**async****let** **tempEggs** **=****boilEggs****()**
**await****toastLabel****.****text****=** **tempToast**
**await****eggLabel****.****text****=** **tempEggs**
   plateAndServeLabel.text = plateAndServe()
   let endTime = Date().timeIntervalSince1970
   elapsedTimeLabel.text = "Elapsed time is
   \(((endTime - startTime) * 100).rounded()
   / 100) seconds"
} 

构建并运行应用。你会发现经过的时间现在比之前短:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_09_08.png

图 9.8:iOS 模拟器运行 BreakfastMaker 应用,显示经过的时间

这是因为使用async-let允许makeToast()poachEgg()方法并行运行,并且poachEgg()方法不再等待makeToast()方法完成后再开始执行。酷!

关于 Swift 并发还有很多东西要学习,比如结构化并发和 actors,但这超出了本章的范围。你可以在developer.apple.com/videos/play/wwdc2021/10134/了解更多关于结构化并发的信息,你可以在developer.apple.com/videos/play/wwdc2021/10133/了解更多关于 actors 的信息。

你已经在你的应用中成功实现了异步代码。太棒了!关于 Swift 并发还有很多东西要学习,比如结构化并发和 actors,但这超出了本章的范围。

给自己鼓掌吧;你已经完成了这本书的第一部分!

摘要

在本章中,您了解了 Swift 并发及其在BreakfastMaker应用程序中的实现方法。

您从学习 Swift 并发的基本概念开始。然后,您检查了一个没有并发的应用程序,并探讨了它的问题。之后,您开启了严格的并发检查,并在应用程序中实现了并发,使用了async/await。最后,您通过使用async let使您的应用程序更加高效。

您现在已经了解了 Swift 并发的基础知识,并将能够在自己的应用程序中使用async/awaitasync-let

在下一章中,您将通过创建它的屏幕,使用故事板来编写您的第一个 iOS 应用程序,这允许您在不输入大量代码的情况下快速原型化应用程序。

留下您的评价!

感谢您从 Packt Publishing 购买此书——我们希望您喜欢它!您的反馈对我们来说无价,它帮助我们改进和成长。一旦您阅读完毕,请花一点时间在亚马逊上留下评价;这只需一分钟,但对像您这样的读者来说意义重大。扫描下面的二维码或访问链接,以获得您选择的免费电子书。

packt.link/NzOWQ

https://packt.link/NzOWQ

第二部分

设计

欢迎来到本书的第二部分。到这个时候,你已经熟悉了 Xcode 的用户界面,并且对使用 Swift 有了坚实的基础。在这一部分,你将开始创建一个名为JRNL的日记应用的用户界面。你将使用 Interface Builder 来构建应用将使用的屏幕,向它们添加按钮、标签和字段等元素,并通过 segues 将它们连接起来。正如你将看到的,你可以用最少的编码来完成这些工作。

本部分包括以下章节:

  • 第十章设置用户界面

  • 第十一章构建用户界面

  • 第十二章完成用户界面

  • 第十三章修改应用屏幕

到这一部分的结尾,你将能够在 iOS 模拟器中导航你应用的各个屏幕,并且将知道如何原型化你自己的应用的用户界面。让我们开始吧!

第十章:设置用户界面

在本书的第一部分中,你学习了 Swift 语言及其工作原理。现在你对这门语言有了很好的了解,你可以学习如何开发 iOS 应用程序。在这一部分,你将构建一个名为JRNL的日记应用程序的用户界面(UI)。你将使用 Xcode 的界面构建器来完成这项工作,并且代码将保持最小化。

你将从这个章节开始学习 iOS 开发中广泛使用的一些有用术语。接下来,你将游览JRNL应用程序中使用的屏幕,并了解用户如何使用该应用程序。最后,你将开始使用界面构建器重新创建应用程序的 UI,从允许用户在日记列表和地图屏幕之间选择的标签栏开始。你将在两个屏幕的顶部添加导航栏并配置标签栏按钮。

到本章结束时,你将学会 iOS 应用开发中常用的术语,了解你的应用程序的流程,以及如何使用界面构建器添加和配置 UI 元素。

本章将涵盖以下主题:

  • 学习 iOS 开发中的有用术语

  • 游览JRNL应用程序

  • 修改你的 Xcode 项目

  • 设置标签栏控制器场景

技术要求

你将修改在第一章,“探索 Xcode”中创建的JRNL Xcode 项目。

本章的资源文件和完成的 Xcode 项目位于本书代码包的Chapter10文件夹中,可以在此处下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Ninth-Edition

观看以下视频,看看代码的实际应用:

youtu.be/lgyerQeTgN4

在你开始项目之前,你将学习一些 iOS 开发中常用的术语。

学习 iOS 开发中的有用术语

当你开始你的 iOS 应用开发之旅时,你将遇到特殊的术语和定义。以下是一些最常用的术语和定义。现在只需阅读它们即可。即使你现在可能并不完全理解,但随着你的深入,一切都会变得清晰:

  • 视图:视图是UIView类或其子类的实例。你屏幕上看到的所有内容(按钮、文本字段、标签等)都是视图。你将使用视图来构建你的 UI。

类在第七章,“类、结构和枚举”中有所介绍。

  • 堆叠视图:堆叠视图是UIStackView类的实例,它是UIView的子类。它用于将视图组合成水平或垂直堆叠,这使得它们更容易使用自动布局(稍后在本节中讨论)在屏幕上定位。自动布局是一种布局方式,它允许开发者通过指定视图之间的相对位置和大小来创建用户界面。

  • 视图控制器:视图控制器是UIViewController类的一个实例。每个视图控制器都有一个view属性,它包含对一个视图的引用。它决定了视图向用户显示的内容以及用户与视图交互时会发生什么。

视图控制器将在第十四章“开始使用 MVC 和表格视图”中详细讨论。

  • 表格视图控制器:表格视图控制器是UITableViewController类的一个实例,它是UIViewController类的一个子类。它的view属性包含对一个UITableView实例(表格视图)的引用,该实例显示一列UITableViewCell实例(表格视图单元格)。

设置应用以表格视图的形式显示你的设备设置:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_01.png

图 10.1:设置应用

正如你所见,所有不同的设置(通用辅助功能隐私等)都在表格视图的单元格内显示。

  • 集合视图控制器:集合视图控制器是UICollectionViewController类的一个实例,它是UIViewController类的一个子类。它的view属性包含对一个UICollectionView实例(集合视图)的引用,该实例显示一个UICollectionViewCell实例(集合视图单元格)的网格。

照片应用在集合视图中显示照片:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_02.png

图 10.2:照片应用

正如你所见,缩略图图片在集合视图中显示在集合视图的单元格内。

  • 导航控制器:导航控制器是UINavigationController类的一个实例,它是UIViewController类的一个子类。它有一个viewControllers属性,该属性包含一个视图控制器数组。数组中最后一个视图控制器的视图会显示在屏幕上,同时屏幕顶部还有一个导航栏。

设置应用中,表格视图控制器嵌入在导航控制器中,你可以看到表格视图上方的导航栏:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_03.png

图 10.3:设置应用中的导航栏

当你点击一个设置时,该设置的视图控制器会被添加到分配给viewControllers属性的视图控制器数组中。用户会看到该视图控制器从右侧滑入。注意屏幕顶部的导航栏,它可以包含标题和按钮。一个**< 设置**按钮出现在导航栏的左上角。点击此按钮会返回上一个屏幕,并从viewControllers属性分配的视图控制器数组中移除该设置的视图控制器。

  • 标签栏控制器:标签栏控制器是UITabBarController类的一个实例,它是UIViewController类的一个子类。它有一个viewControllers属性,包含一个视图控制器数组。数组中第一个视图控制器的视图显示在屏幕上,同时还有一个带有按钮的标签栏在底部。最左边的按钮对应于数组中的第一个视图控制器,并且已经选中。当你点击另一个按钮时,相应的视图控制器将被加载,其视图将显示在屏幕上。

Fitness应用使用标签栏控制器来导航到不同的屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_04.png

图 10.4:健身应用中的标签栏

正如你所见,这个应用的不同屏幕(所有照片为你推荐相册搜索)可以通过点击相应的标签栏按钮来访问。

  • 模型-视图-控制器(MVC):这是在 iOS 应用开发中非常常见的一个设计模式。用户与屏幕上的视图进行交互。应用数据存储在数据模型对象中。控制器管理视图和数据模型对象之间的信息流。

    MVC 将在第十四章开始使用 MVC 和表格视图中详细讨论。

  • 故事板文件:故事板文件包含用户看到的视觉表示。应用中的每一屏幕都由一个故事板场景表示。

打开你在第一章探索 Xcode中创建的JRNL项目,并点击故事板文件。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_05.png

图 10.5:显示主故事板文件的 JRNL Xcode 项目

你将看到一个场景,当你运行你的应用在模拟器中时,这个场景的内容将显示在屏幕上。你可以在故事板文件中有一个以上的场景。

  • 转场:如果你在一个应用中有多个场景,你使用转场来从一个场景移动到另一个场景。JRNL项目没有转场,因为它的故事板文件中只有一个场景,但你将在本章的后面部分看到它们。

  • 自动布局:作为一名开发者,你必须确保你的应用在不同屏幕尺寸的设备上看起来都很好。自动布局帮助你根据你指定的约束来布局你的 UI。例如,你可以设置一个约束来确保按钮在屏幕上居中,无论屏幕大小如何,或者当设备从纵向旋转到横向时,使文本字段扩展到屏幕的宽度。

现在你已经熟悉了在 iOS 应用开发中使用的术语,让我们来游览一下你将要构建的应用。

JRNL 应用的游览

让我们快速浏览一下你将要构建的应用程序。JRNL应用程序是一个日记应用程序,允许用户编写自己的个人日记,并为每个日记条目提供存储照片或地图位置的选择。用户还可以查看显示靠近用户当前位置的条目位置的地图。你将在下一节中看到应用程序中使用的所有屏幕及其整体流程。

使用“期刊列表”屏幕

当应用程序启动时,你会看到“期刊列表”屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_06.png

图 10.6:期刊列表屏幕

让我们研究一下这个屏幕的不同部分。

屏幕底部的UITabBar实例(标签栏)显示了期刊地图按钮。期刊按钮被选中,你可以看到一个表格视图,在表格视图中显示期刊条目的列表。一个UISearchController实例在屏幕顶部显示一个搜索栏。这允许你搜索特定的期刊条目。

要添加新的期刊条目,你点击屏幕顶部的**+**按钮。这会显示“添加新期刊条目”屏幕。

使用“添加新期刊条目”屏幕

当你在“期刊列表”屏幕顶部点击**+**按钮时,你会看到“添加新期刊条目”屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_07.png

图 10.7:添加新期刊条目屏幕

让我们研究一下这个屏幕的不同部分。

屏幕顶部的导航栏包含取消保存按钮。一个堆叠视图显示自定义评分控件、开关、条目标题文本字段、正文文本视图和占位符照片。点击评分控件可以为此条目分配 0 到 5 星。打开开关将获取你的当前位置。

你可以在条目标题文本字段中输入期刊条目的标题,并在正文文本视图中输入详细信息。你还可以点击占位符照片,使用设备相机拍照。一旦你点击保存,你将返回到“期刊列表”屏幕,然后新的条目将在表格视图中可见。你也可以点击取消,不创建新的期刊条目就返回到“期刊列表”屏幕。

要查看特定期刊条目的详细信息,点击列表中的条目,然后你会看到“期刊条目详情”屏幕。

使用“期刊条目详情”屏幕

点击“期刊列表”屏幕上的任何一条期刊条目将显示相应的“期刊条目详情”屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_08.png

图 10.8:期刊条目详情屏幕

让我们研究一下这个屏幕的不同部分。

屏幕顶部的导航栏包含一个返回按钮。一个表格视图在表格视图中显示期刊条目的日期、评分、标题文本、正文文本、照片和位置地图。

你可以点击**< 期刊**按钮返回到“期刊列表”屏幕。

使用“地图”屏幕

在标签栏中点击地图按钮会显示地图屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_09.png

图 10.9:地图屏幕

让我们研究一下这个屏幕的不同部分。

屏幕底部的标签栏显示了JournalMap按钮。Map按钮被选中,你可以看到一个MKMapView实例(地图视图)在屏幕上显示地图,图钉指示期刊条目。

点击一个图钉将显示注释,点击注释中的按钮将显示该条目的期刊条目详情屏幕。

这完成了对该应用的浏览。现在,是时候开始构建它的 UI 了!

修改你的 Xcode 项目

现在你已经知道了应用屏幕的外观,你可以开始构建它了。如果你还没有这样做,打开你在第一章 探索 Xcode中创建的JRNL项目:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_10.png

图 10.10:JRNL 项目

确认从目标菜单中选择了iPhone SE (第 3 代)。构建并运行你的应用。你会看到一个空白的白色屏幕。如果你在项目导航器中点击Main故事板文件,你会看到它包含一个包含空白视图的单个场景。这就是为什么当你运行应用时,你只看到一个空白的白色屏幕。

要配置 UI,你将使用 Interface Builder 修改Main故事板文件。Interface Builder 允许你添加和配置场景。每个场景代表用户将看到的屏幕。你可以在场景中添加 UI 对象,如视图和按钮,并按需配置它们,使用属性检查器。

有关如何使用 Interface Builder 的更多信息,请访问此链接:help.apple.com/xcode/mac/current/#/dev31645f17f

现在,你将在标签栏中嵌入现有的场景,并向其中添加另一个场景。标签栏场景将在屏幕底部显示一个标签栏,其中包含两个按钮。点击一个按钮将显示与之关联的屏幕。这些屏幕对应于应用浏览中显示的期刊列表和地图屏幕。让我们看看如何在下一节中完成这个操作。

设置标签栏控制器场景

正如你在应用浏览中看到的,JRNL应用在屏幕底部有一个标签栏,其中包含两个按钮,用于显示期刊列表和地图屏幕。你将在标签栏中嵌入现有的视图控制器场景,并向其中添加第二个视图控制器场景。按照以下步骤操作:

  1. 在项目导航器中点击Main故事板文件:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_11.png

图 10.11:选择 Main 故事板文件的项目导航器

Main故事板文件的内容显示在编辑器区域。

  1. 如果它不存在,点击文档大纲按钮以显示文档大纲:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_12.png

图 10.12:显示文档大纲按钮的编辑器区域

  1. 在文档大纲中选择View Controller

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_13.png

图 10.13:选择 ViewController 的文档大纲

  1. 你将在标签栏控制器场景中嵌入现有的视图控制器场景。从编辑器菜单中选择嵌入 | 标签栏控制器

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_14.png

图 10.14:选中嵌入 | 标签栏控制器的编辑器菜单

你将在编辑器区域看到一个新标签栏控制器场景出现。

  1. 点击窗口右上角的**+**按钮以显示库:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_15.png

图 10.15:显示+按钮的工具栏

库允许你选择要添加到场景中的 UI 对象。

  1. 在库的过滤器字段中输入view con。一个View Controller对象将出现在结果列表中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_16.png

图 10.16:选中视图控制器对象的库

  1. View Controller对象拖动到故事板中,以添加一个新的视图控制器场景,并将其放置在现有的视图控制器场景下方:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_17.png

图 10.17:添加了视图控制器场景的主故事板文件

  1. 点击**-**按钮以缩小视图,并在故事板中重新排列场景,以便同时显示标签栏控制器场景和视图控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_18.png

图 10.18:显示缩放按钮的编辑区域

如果**–+按钮不可见,尝试放大 Xcode 窗口。你也可以尝试使用导航器检查器按钮隐藏导航器检查器**区域。

  1. 在文档大纲中选择Tab Bar Controller。按Ctrl键并从Tab Bar Controller拖动到新添加的视图控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_19.png

图 10.19:显示拖动目的地的编辑区域

  1. 将出现一个切换弹出菜单。从该菜单中选择视图控制器

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_20.png

图 10.20:切换弹出菜单

将出现一个连接标签栏控制器场景到视图控制器场景的切换。

  1. 编辑器区域重新排列场景,使其看起来像下面的截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_21.png

图 10.21:重新排列场景的编辑区域

  1. 在模拟器中构建和运行你的应用,你将在屏幕底部看到带有两个按钮的标签栏:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_22.png

图 10.22:显示两个按钮的标签栏的模拟器

你已成功将标签栏添加到项目中,但正如你所见,按钮标题目前都命名为Item。在下一节中,你将将它们更改为JournalMap

设置标签栏按钮标题和图标

你的应用现在在屏幕底部显示了一个标签栏,但按钮标题和图标与应用之旅中显示的不匹配。为了使它们匹配,你将在属性检查器中将按钮标题配置为读取JournalMap,并配置它们的图标。按照以下步骤操作:

  1. 在项目导航器中点击Main故事板文件。如果未显示,点击文档大纲按钮以显示文档大纲。点击文档大纲中的第一个Item Scene

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_23.png

图 10.23:显示已选择第一个项目场景的文档大纲

  1. 项目场景下点击项目按钮。然后,点击属性检查器按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_24.png

图 10.24:已选择属性检查器

  1. 条目项下将标题设置为Journal图像设置为person.fill

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_25.png

图 10.25:属性检查器,标题设置为 Journal,图像设置为 person.fill

  1. 在第二个项目场景中点击项目按钮,并在条目项下将标题设置为Map图像设置为map

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_26.png

图 10.26:属性检查器,标题设置为 Map,图像设置为 map

  1. 在模拟器中构建并运行你的应用。你会看到按钮的标题已分别更改为JournalMap,并且每个按钮都有一个自定义图标:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_27.png

图 10.27:显示带有自定义按钮标题和图标的标签栏的模拟器

点击JournalMap按钮将显示期刊列表和地图屏幕的场景。

person.fillmap图标是苹果的SF Symbols库的一部分。要了解更多信息,请访问此链接:developer.apple.com/design/human-interface-guidelines/sf-symbols

正如你在应用浏览中看到的,一些屏幕在导航栏中有标题和按钮。在下一节中,你将学习如何将导航栏添加到你的屏幕上,以便你可以根据需要稍后添加按钮和标题。

在导航控制器中嵌入视图控制器

正如你在应用浏览中看到的,期刊列表和地图屏幕的顶部都有导航栏。要为两个屏幕添加导航栏,你需要在导航控制器中嵌入期刊和地图场景的视图控制器。这样,当显示期刊列表和地图屏幕时,导航栏将出现在屏幕顶部。按照以下步骤操作:

  1. 在文档大纲中点击期刊场景

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_28.png

图 10.28:显示已选择期刊场景的文档大纲

  1. 编辑菜单中选择嵌入 | 导航控制器

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_29.png

图 10.29:显示已选择嵌入到导航控制器中的编辑菜单

  1. 验证是否在标签栏控制器场景和期刊场景之间出现了一个导航控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_30.png

图 10.30:显示已添加导航控制器场景的编辑区域

  1. 在文档大纲中点击地图场景并重复步骤 2

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_31.png

图 10.31:显示已添加导航控制器场景的编辑区域

现在,期刊列表屏幕和地图屏幕都拥有导航栏,但由于它们的颜色与背景相同,所以在屏幕上并不明显。你将为每个场景的导航项设置标题,以便区分它们。

  1. 在文档大纲中选择第一个视图控制器场景导航项。在属性检查器中,在导航项下,将标题设置为日历

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_32.png

图 10.32:标题设置为“日历”的属性检查器

  1. 在文档大纲中选择第二个视图控制器场景导航项。在属性检查器中,在导航项下,将标题设置为地图

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_33.png

图 10.33:标题设置为“地图”的属性检查器

  1. 构建并运行你的应用,并点击每个标签栏按钮以显示相应的屏幕。注意每个屏幕在导航栏中显示一个标题。

在导航控制器中嵌入视图控制器会将该视图控制器添加到导航控制器的viewControllers数组中。然后导航控制器在屏幕上显示视图控制器的视图。导航控制器还会在屏幕顶部显示一个带有标题的导航栏。

恭喜!你刚刚为你的应用配置了标签栏和导航控制器!

你可能已经注意到 Interface Builder 中显示的屏幕与你在目标菜单中选择的 iPhone 型号不匹配,你可能还会发现最小地图显示会妨碍你在应用中排列屏幕。让我们进行一些额外的 Interface Builder 配置来解决这个问题。

配置 Xcode

尽管你已经配置了模拟器使用 iPhone SE(第 3 代)作为你的应用设备,但在 Interface Builder 中显示的场景是为不同的 iPhone 型号。你可能还希望隐藏最小地图显示。让我们配置 Interface Builder 中的场景以使用 iPhone SE(第 3 代)并隐藏最小地图显示。按照以下步骤操作:

  1. 应该仍然选择故事板文件。要配置在 Interface Builder 中场景的外观,请点击设备配置按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_34.png

图 10.34:显示设备配置按钮的编辑区域

将显示不同设备屏幕的弹出窗口。

  1. 从此弹出窗口中选择iPhone SE(第 3 代),然后点击编辑区域中的任何位置以关闭它:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_35.png

图 10.35:选择 iPhone SE(第 3 代)的设备弹出窗口

故事板中场景的外观将更改为反映 iPhone SE(第 3 代)的屏幕。

  1. 如果你希望隐藏最小地图,从编辑菜单中选择最小地图以取消选中它。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_36.png

图 10.36:突出显示最小地图的编辑菜单

  1. 验证在故事板文件中是否有以下场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_10_37.png

图 10.37:显示完成的主故事板文件的编辑区域

  1. 构建并运行你的应用。它应该和之前一样工作。

你已经为你的应用创建了日历列表和地图屏幕!做得好!

摘要

在本章中,你学习了在 iOS 应用开发中使用的某些有用术语。这将使你更容易理解本书的其余部分,以及关于该主题的其他书籍或在线资源。

然后,你还了解了 JRNL 应用中使用的不同屏幕以及用户如何使用该应用。当你从头开始重新创建应用的 UI 时,你能够将你所做的工作与实际应用的外观进行比较。

最后,你学习了如何使用 Interface Builder 和 storyboards 将标签栏控制器场景添加到你的应用中,配置按钮标题和图标,并为 JournalList 和 Map 屏幕添加导航控制器。这将使你熟悉为你的应用添加和配置 UI 元素。

在下一章中,你将继续设置你应用的 UI,并熟悉更多 UI 元素。你将为你的应用添加和配置剩余的屏幕。

加入我们的 Discord 社区!

与其他用户、专家和作者本人一起阅读这本书。提出问题,为其他读者提供解决方案,通过 Ask Me Anything 会话与作者聊天,等等。扫描二维码或访问链接加入社区。

packt.link/ios-Swift

https://packt.link/ios-Swift

第十一章:构建你的用户界面

在上一章中,你修改了一个现有的 Xcode 项目,向你的应用添加了一个标签栏,允许用户在日记列表和地图屏幕之间进行选择,并配置了标签栏按钮的标题和图标。当你的应用启动时,显示的是日记列表屏幕,但它目前是空的。

正如你在第十章设置用户界面中的应用游览中看到的那样,日记列表屏幕应显示一个表格视图,显示表格视图单元格中的日记条目列表。

在本章中,你将使日记列表屏幕显示一个包含 10 个空表格视图单元格的表格视图,以及一个按钮,当点击时将显示一个表示添加新日记条目屏幕的视图。你还将配置一个取消按钮来关闭此视图并返回到日记列表屏幕。

你将在你的应用中添加少量代码,但不用担心太多——你将在本书的下一部分中了解更多关于它的内容。

到本章结束时,你将学会如何向故事板场景添加视图控制器,将视图控制器中的输出连接到场景,设置表格视图单元格,并以模态方式呈现视图控制器。这在你设计自己的应用的用户界面时将非常有用。

本章将涵盖以下主题:

  • 将表格视图添加到日记列表屏幕

  • 将故事板元素连接到视图控制器

  • 为表格视图配置数据源方法

  • 以模态方式呈现视图

技术要求

你将继续在上一章中创建的 JRNL Xcode 项目上工作。

本章的完成 Xcode 项目位于本书代码包的 Chapter11 文件夹中,可以在此处下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Ninth-Edition

查看以下视频以查看代码的实际效果:

youtu.be/EsDaVgrGLus

让我们从向日记列表屏幕添加表格视图开始,该表格视图最终将显示日记条目列表。

将表格视图添加到日记列表屏幕

正如你在应用游览中看到的那样,JRNL 应用在表格视图中显示日记条目。表格视图是 UITableView 类的一个实例。它显示一列单元格。表格视图中的每个单元格都是一个表格视图单元格,它是 UITableViewCell 类的一个实例。在本节中,你将首先在 Main 故事板文件中为日记列表屏幕的视图控制器场景添加一个表格视图,然后你将添加自动布局约束使其填充整个屏幕。

有关自动布局及其使用方法的更多信息,请访问 developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/

打开您在前一章中创建的JRNL项目并运行应用程序,以确保一切仍然按预期工作,然后按照以下步骤操作:

  1. 在项目导航器中点击故事板文件,选择代表期刊列表屏幕的视图控制器场景,然后点击库按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_01.png

图 11.1:显示库按钮的工具栏

  1. 库将出现。在过滤器字段中输入table表格视图对象将作为结果之一出现。将其拖动到期刊列表屏幕视图控制器场景的中间:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_02.png

图 11.2:选择表格视图对象的库

表格视图已添加,但它只占据了屏幕的一小部分。如前一章中的应用程序导游所示,它应该填充整个屏幕。

  1. 您将使用自动布局添加新约束按钮将表格视图的边缘绑定到其封装视图的边缘。确保表格视图被选中,然后点击自动布局添加新约束按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_03.png

图 11.3:选择表格视图的视图控制器场景

  1. 在顶部、左侧、右侧和底部边缘约束字段中输入0,然后点击所有浅红色支柱。确保所有支柱都已变为亮红色。点击添加 4 个约束按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_04.png

图 11.4:添加新约束的自动布局弹出对话框

这将设置表格视图边缘与封装视图边缘之间的空间为 0,将表格视图的边缘绑定到封装视图的边缘。现在表格视图将填充屏幕,无论设备方向如何。

  1. 确认表格视图的四面现在都已绑定到屏幕边缘,如下面的截图所示:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_05.png

图 11.5:表格视图填充屏幕的视图控制器场景

您已将表格视图添加到期刊列表屏幕视图控制器场景的视图中,并使用自动布局约束使其填充整个屏幕,但构建并运行您的应用程序时,期刊列表屏幕仍然会保持空白。

在下一节中,您将实现JournalListViewController类的代码,并将此类的出口连接到期刊列表屏幕上的 UI 元素。这将使JournalListViewController类的一个实例能够控制期刊列表屏幕显示的内容。

将故事板元素连接到视图控制器

您已将表格视图添加到期刊列表屏幕,但它目前还没有显示任何内容。您需要修改现有的视图控制器以在期刊列表屏幕中管理表格视图。当您创建JRNL项目时,Xcode 自动创建了ViewController文件。

它包含了名为 ViewControllerUIViewController 子类的声明和定义,并且这个类目前被设置为 Journal List 屏幕的视图控制器。你需要在 ViewController 文件中将类的名称更改为 JournalListViewController,并为之前添加到视图控制器场景中的表格视图创建一个出口。按照以下步骤操作:

  1. 在项目导航器中点击 ViewController 文件。在编辑区域,右键点击类名,选择 重构 | 重命名

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_06.png

图 11.6:编辑区域显示带有重命名…高亮的弹出菜单

  1. 将类名更改为 JournalListViewController 并点击 重命名

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_07.png

图 11.7:显示 ViewController 类新名称的编辑区域

  1. 确认类名和文件名都已更改为 JournalListViewController

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_08.png

图 11.8:文件名和类名都更改为 JournalListViewController

  1. 在项目导航器中点击 Main 故事板文件,并在文档大纲中选择第一个 Journal Scene(包含表格视图的那个)。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_09.png

图 11.9:文档大纲显示已选择第一个 Journal Scene

  1. 点击身份检查器按钮,并确认在 自定义类 下, 设置为 JournalListViewController

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_10.png

图 11.10:身份检查器中类设置为 JournalListViewController

这意味着 Journal List 屏幕的内容由 JournalListViewController 类的一个实例管理。

  1. 点击导航器和检查器按钮以隐藏导航器和检查器区域,以便有更多空间工作:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_11.png

图 11.11:工具栏显示导航器和检查器按钮

  1. 点击调整编辑器选项按钮,并从弹出菜单中选择 辅助

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_12.png

图 11.12:调整编辑器选项菜单中已选择辅助

这将在辅助编辑器中显示与此场景关联的任何 Swift 文件。如你所见,Main 故事板文件的内容显示在编辑区域的左侧,而 JournalListViewController 类的定义显示在右侧。

  1. 查看代码上方的小条,确认 JournalListViewController.swift 已被选中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_13.png

图 11.13:显示已选择的 JournalListViewController.swift

如果你没有看到 JournalListViewController.swift 被选中,点击条并从弹出菜单中选择 JournalListViewController.swift

  1. 要将 Journal 场景中的表格视图连接到 JournalListViewController 类中的出口,从表格视图 Ctrl + 拖动 到类名声明下方的 JournalListViewController 文件:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_14.png

图 11.14:显示拖拽目标位置的编辑区域

你也可以从文档大纲中的表格视图拖动。

  1. 将会弹出一个小的弹出对话框。在 名称 文本框中输入出口名称,tableView,将 存储 设置为 ,然后点击 连接

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_15.png

图 11.15:出口创建的弹出对话框

  1. 验证 tableView 出口声明是否已自动添加到 JournalListViewController 类。完成此操作后,点击 x 关闭辅助编辑器窗口:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_16.png

图 11.16:显示 tableView 出口的编辑区域

JournalListViewController 类现在在 Journal List 屏幕中有一个出口,tableView。这意味着一个 JournalListViewController 实例可以管理表格视图显示的内容。

使用 Ctrl + 拖动 从故事板场景中的元素拖动到文件时,经常会出错。如果你在这个过程中出错,这可能会导致应用启动时崩溃。为了检查表格视图和 JournalListViewController 类之间的连接是否存在错误,请按照以下步骤操作:

  1. 点击导航器和检查器按钮以显示导航器和检查器区域。

  2. Journal Scene 中选择 Journal,然后点击连接检查器按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_17.png

图 11.17:选择连接检查器

连接检查器显示你的 UI 对象和代码之间的链接。你将在 出口 部分看到 tableView 出口连接到表格视图。

  1. 如果你看到一个微小的黄色警告图标,点击 x 来断开连接:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_18.png

图 11.18:显示带有黄色警告图标的 tableView 出口的连接检查器

  1. 出口 下,从 tableView 出口拖动到表格视图以重新建立连接:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_19.png

图 11.19:显示要连接的表格视图的编辑区域

如果你需要在创建后更改代码中的出口名称,右键单击出口名称,然后从弹出菜单中选择 重构 | 重命名 而不是手动更改,以避免错误。

你已经在 JournalListViewController 类中成功创建了一个用于表格视图的出口。做得好!

要在屏幕上显示表格视图单元格,你需要通过向 JournalListViewController 类添加一些代码来实现表格视图的数据源方法。你将在下一节中这样做。

配置表格视图的数据源方法

当您的应用运行时,JournalListViewController 类的一个实例充当 Journal 列表屏幕的视图控制器。它负责加载和显示该屏幕上的所有视图,包括您之前添加的表格视图。表格视图需要知道要显示多少个表格视图单元格以及每个单元格中要显示什么。通常,视图控制器负责提供这些信息。苹果为这个目的创建了一个协议,UITableViewDataSource。您需要做的只是将表格视图的 dataSource 属性设置为 JournalListViewController 类,并实现此协议的必需方法。

表格视图还需要知道如果用户点击表格视图单元格时应该做什么。同样,表格视图的视图控制器负责此事,而苹果为这个目的创建了 UITableViewDelegate 协议。您将设置表格视图的 delegate 属性为 JournalListViewController 类,但您目前不会实现此协议中的任何方法。

协议在 第八章协议、扩展和错误处理 中有所介绍。

您在本章中需要输入少量代码。不用担心它的含义;您将在本书的 第三部分 中学习更多关于表格视图控制器及其相关协议的内容。

在下一节中,您将使用连接检查器将表格视图的 dataSourcedelegate 属性分配给 JournalListViewController 类中的输出。

设置表格视图的委托和数据源属性

JournalListViewController 类的一个实例将提供表格视图将显示的数据,以及当用户与表格视图交互时将执行的方法。为了使这生效,您需要将表格视图的 dataSourcedelegate 属性连接到 JournalListViewController 类中的输出。按照以下步骤操作:

  1. 如果您还没有这样做,请点击导航器和检查器按钮以再次显示导航器和检查器区域。

  2. 故事板文件仍然被选中。在文档大纲中选择 Journal Scene表格视图,然后点击连接检查器按钮。在 输出 部分,您将在 dataSourcedelegate 输出旁边看到两个空圆圈。从每个空圆圈拖动到文档大纲中的 Journal 图标:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_20.png

图 11.20:连接检查器显示 dataSource 和 delegate 输出

  1. 验证表格视图的 dataSourcedelegate 属性是否已连接到 JournalListViewController 类中的输出:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_21.png

图 11.21:已设置 dataSource 和 delegate 输出的连接检查器

在下一节中,你将添加一些代码,使 JournalListViewController 类符合 UITableViewDataSource 协议,并在运行你的应用时配置表格视图以显示 10 个表格视图单元格。

采用 UITableViewDataSource 和 UITableViewDelegate 协议

到目前为止,你已经使 JournalListViewController 类成为表格视图的数据源和代理。下一步是使其采用 UITableViewDataSourceUITableViewDelegate 协议并实现任何必需的方法。你还将更改表格视图单元格的颜色,使它们在屏幕上可见。按照以下步骤操作:

  1. 在文档大纲中点击 表格视图 并点击属性检查器按钮。在 表格视图 下,将 原型单元格 的数量更改为 1

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_22.png

图 11.22:属性检查器显示原型单元格设置为 1

  1. 在文档大纲中点击 表格视图 旁边的 > 按钮,以显示 表格视图单元格

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_23.png

图 11.23:文档大纲显示 > 按钮

这表示表格视图将显示的表格视图单元格。

  1. 在文档大纲中点击 表格视图单元格。在 表格视图单元格 下的 属性检查器 中,将 标识符 设置为 journalCell 并按 Enter 键:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_24.png

图 11.24:属性检查器中已设置标识符

文档大纲中的 表格视图单元格 名称将更改为 journalCell

  1. 视图 下的属性检查器中,将 背景 设置为 系统青色,这样在运行应用时可以轻松看到表格视图单元格:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_25.png

图 11.25:属性检查器中表格视图单元格背景色已设置

  1. 在项目导航器中点击 JournalListViewController 文件。在类声明之后输入以下代码,使 JournalListViewController 类采用 UITableViewDataSourceUITableViewDelegate 协议:

    class JournalListViewController: UIViewController**,** **UITableViewDataSource****,** **UITableViewDelegate** { 
    

几秒钟后,将出现一个错误:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_26.png

图 11.26:编辑区域显示错误

  1. 点击它以显示错误信息。错误信息显示 类型‘JournalListViewController’不符合协议‘UITableViewDataSource’添加符合性的占位符

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_27.png

图 11.27:显示错误信息的编辑区域

这意味着你需要实现 UITableViewDataSource 协议的必需方法,以使 JournalListViewController 符合该协议。

  1. 点击 修复 以自动将必需方法的占位符添加到 JournalListViewController 类中。

  2. 验证两个必需的 UITableViewDataSource 协议方法占位符已自动插入到 JournalListViewController 类中,如图所示:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_28.png

图 11.28:显示 UITableViewDataSource 方法占位符的编辑区域

第一个方法告诉表格视图显示多少个单元格,而第二个方法告诉表格视图在每个表格视图单元格中显示什么。

  1. 将第一个方法中的占位符文本替换为10(如果只是一行代码,则return关键字是可选的)。这告诉表格视图显示 10 个单元格:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_29.png

图 11.29:显示显示 10 个表格视图单元格的代码的编辑区域

  1. 将第二个方法中的占位符文本替换为以下代码:

    tableView.dequeueReusableCell(withIdentifier: "journalCell", for: indexPath) 
    

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_30.png

图 11.30:显示为每行显示一个表格视图单元格的代码的编辑区域

不要担心这现在意味着什么,因为您将在第三部分“表格视图”中学习更多关于表格视图的内容。

  1. 构建并运行您的应用。模拟器将显示 10 个青色表格视图单元格的列,如图所示:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_31.png

图 11.31:显示 10 个表格视图单元格的模拟器

正如您在第十章“设置用户界面”的应用程序游览中看到的那样,屏幕右上角应该有一个**+**按钮。您将在下一节中添加此按钮。

以模态方式呈现视图

期刊列表屏幕的导航栏可以配置为显示标题和按钮。您已经在第十章“设置用户界面”中配置了标题。现在您将向导航栏添加并配置一个栏按钮项。当点击时,此按钮将显示一个表示添加新期刊条目屏幕的视图。此视图将来自一个嵌入在导航控制器中的新视图控制器场景,您将将其添加到项目中。视图将以模态方式呈现,这意味着在它被关闭之前,您将无法执行其他任何操作。

要关闭它,您将在视图的导航栏中添加一个取消按钮。您还将添加一个保存按钮,但您将在第十六章“在视图控制器之间传递数据”中实现其功能。让我们先在下一节中将库中的栏按钮项添加到导航栏中。

向导航栏添加栏按钮

正如第十章“设置用户界面”的应用程序游览中所示,屏幕右上角有一个**+**按钮。为了实现这一点,您将向期刊列表屏幕的导航栏添加一个栏按钮项。按照以下步骤操作:

  1. 在项目导航器中单击Main故事板文件。确保在文档大纲中选择第一个Journal Scene。单击库按钮以显示库:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_32.png

图 11.32:显示库按钮的工具栏

  1. 在过滤器字段中键入bar b。一个栏按钮项对象将出现在结果中。将栏按钮对象拖到导航栏的右侧:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_33.png

图 11.33:选择栏按钮项对象的库

  1. 在选择栏按钮后,单击属性检查器按钮。在栏按钮项下,将系统项设置为添加

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_34.png

图 11.34:将系统项设置为添加的属性检查器

你现在在导航栏中有一个 + 按钮。在下一节中,你将添加一个视图控制器场景来表示当按钮被点击时出现的添加新日记条目屏幕。

添加新的视图控制器场景

第十章 中应用程序浏览所示,在 设置用户界面 时,当你点击导航栏中的 + 按钮时,将显示添加新日记条目屏幕。你将在项目中添加一个新的视图控制器场景来表示此屏幕。按照以下步骤操作:

  1. 点击库按钮以显示库,并在过滤器字段中输入 view con视图控制器 对象将在搜索结果中。将 视图控制器 对象拖放到故事板中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_35.png

图 11.35:库中已选择视图控制器对象

  1. 将视图控制器放置在 日记 场景的右侧:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_36.png

图 11.36:显示视图控制器场景与日记场景并排的编辑区域

  1. 选择新添加的视图控制器场景。在文档大纲中,点击此场景的 视图控制器 图标:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_37.png

图 11.37:选择视图控制器的文档大纲

  1. 你将需要为 取消保存 按钮留出空间,因此你将在这个视图控制器场景中嵌入导航控制器以提供一个可以放置按钮的导航栏。从 编辑 菜单中选择 嵌入 | 导航控制器

  2. 验证是否在视图控制器场景左侧出现了一个导航控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_38.png

图 11.38:显示嵌入在导航控制器中的视图控制器场景的编辑区域

  1. 在文档大纲中点击新视图控制器场景的 导航项。在属性检查器中,在 导航项 下,将 标题 设置为 新条目

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_39.png

图 11.39:属性检查器中标题设置为“新条目”

导航项的名称将更改为 新条目

  1. Ctrl + 拖动从按钮到导航控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_40.png

图 11.40:显示拖动目标的编辑区域

  1. 将出现切换弹出菜单。选择 以模态方式呈现

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_41.png

图 11.41:选择“以模态方式呈现”的切换弹出菜单

当按钮被点击时,这会使视图控制器视图从屏幕底部向上滑动。在此视图关闭之前,你无法与其他任何视图进行交互。

  1. 验证是否有一个切换将 日记 场景和 导航控制器 场景连接在一起:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_42.png

图 11.42:显示从日记场景到导航控制器场景的切换的编辑区域

  1. 构建并运行你的应用。点击 + 按钮,新的视图控制器视图将从屏幕底部向上滑动:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_43.png

图 11.43:模拟器显示新的视图控制器视图

目前您只能通过向下拖动来取消此视图。在下一节中,您将在导航栏中添加一个取消按钮,并编程使其取消视图。您还会添加一个保存按钮,但暂时不会对其进行编程。

在导航栏中添加取消和保存按钮

如您之前所见,将视图控制器嵌入导航控制器的一个好处是屏幕顶部的导航栏。您可以在其左右两侧放置按钮。按照以下步骤将取消保存按钮添加到导航栏:

  1. 点击文档轮廓中新条目场景的导航项。点击图书馆按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_44.png

图 11.44:显示图书馆按钮的工具栏

  1. 在过滤器字段中输入bar b,并将栏按钮项对象拖到导航栏的每一侧:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_45.png

图 11.45:选中栏按钮项对象的图书馆

  1. 点击右侧的项目按钮。在栏按钮项的属性检查器下,将样式设置为完成,并将系统项设置为保存

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_46.png

图 11.46:将样式设置为完成并将系统项设置为保存的属性检查器

  1. 点击左侧的项目按钮,并将系统项设置为取消

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_47.png

图 11.47:将系统项设置为取消的属性检查器

请记住,导航控制器有一个属性,viewControllers,它包含一个视图控制器数组。当您在期刊列表屏幕上点击**+**按钮时,新的视图控制器将被添加到viewControllers数组中,其视图从屏幕底部出现,覆盖期刊列表屏幕,唯一取消视图的方法是向下拖动。

  1. 要使取消按钮能够取消视图,您需要将取消按钮链接到场景退出,并在JournalListViewController类中实现一个方法,该方法将在期刊列表屏幕重新出现时执行。在项目导航器中,点击JournalListViewController文件,并在文件底部在最后的闭合花括号之前添加以下方法:

    @IBAction func unwindNewEntryCancel(segue:
    UIStoryboardSegue) {
    } 
    
  2. 在项目导航器中,点击故事板文件,并在新条目场景中点击取消按钮。在文档轮廓中,Ctrl + 拖动取消按钮到场景退出图标,并从弹出菜单中选择unwindNewEntryCancelWithSegue:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_48.png

图 11.48:文档轮廓显示正在设置取消按钮动作

当您的应用运行时,点击取消按钮将从导航控制器的viewControllers数组中移除视图控制器,取消所呈现的模态视图,并执行unwindNewEntryCancel(segue:)方法。请注意,此方法目前没有任何操作。

  1. 构建并运行你的应用程序,点击 Journal List 屏幕导航栏中的**+按钮。新视图将出现。当你点击取消**按钮时,新视图将消失:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_11_49.png

图 11.49:模拟器显示的取消按钮

恭喜!你已经完成了 Journal List 屏幕的基本结构!

摘要

在本章中,你将表格视图添加到主 Storyboard 文件中的 Journal List 屏幕,并修改现有的视图控制器类以实现JournalListViewController类。然后,你修改了JournalListViewController类,使其在 Storyboard 中有一个表格视图出口,并使其成为表格视图的数据源和代理。最后,你添加了一个按钮来显示第二个视图,并配置了一个取消按钮来关闭它。

到目前为止,你应该已经熟练使用 Interface Builder 向 Storyboard 场景添加视图和视图控制器,将视图控制器出口链接到 Storyboard 中的 UI 元素,设置表格视图,以及以模态方式显示视图。当你设计自己的应用程序的用户界面时,这将非常有用。

在下一章中,你将实现应用程序的 Journal Entry Detail 屏幕,并为 Map 屏幕实现一个地图视图。

加入我们的 Discord!

与其他用户、专家以及作者本人一起阅读这本书。提出问题,为其他读者提供解决方案,通过“问我任何问题”的环节与作者聊天,以及更多。扫描二维码或访问链接加入社区。

packt.link/ios-Swift

https://packt.link/ios-Swift

第十二章:完成用户界面

在上一章中,你配置了期刊列表屏幕以显示表格视图中的 10 个空表格视图单元格,向导航栏中添加了一个条形按钮项以模态方式呈现表示添加新期刊条目屏幕的视图,并添加了取消保存按钮。

在本章中,你将添加第十章中应用程序之旅中显示的剩余屏幕。你将添加期刊条目详情屏幕,当在期刊列表屏幕中轻按表格视图单元格时将显示此屏幕。你将配置此屏幕以显示具有固定数量表格视图单元格的表格视图。你还将使地图屏幕显示地图。

到本章结束时,你将学会如何将具有固定单元格数的表格视图添加到故事板场景中,如何实现当在期刊列表屏幕中轻按单元格时显示屏幕的 segue,以及如何将地图视图添加到场景中。你的应用程序的基本用户界面将完整,你将能够在模拟器中遍历所有屏幕。所有屏幕都不会显示数据,但你将在本书的第三部分中完成它们的实现。

本章将涵盖以下主题:

  • 实现期刊条目详情屏幕

  • 将地图视图添加到地图屏幕

技术要求

你将继续在上一章中创建的JRNL项目中工作。

本章的完成 Xcode 项目位于本书代码包的Chapter12文件夹中,可以在此处下载:

github.com/PacktPublishing/iOS-18-Programming-for-Beginners-Ninth-Edition

查看以下视频以查看代码的实际效果:

youtu.be/TFaELYrzyxY

首先,你将向故事板添加一个新的表格视图控制器场景来表示期刊条目详情屏幕。当在期刊列表屏幕中轻按单元格时,将显示此屏幕。你将在下一节中这样做。

实现期刊条目详情屏幕

第十章中应用程序之旅所示,在设置用户界面时,当你轻按期刊列表屏幕中的期刊条目时,将出现包含该期刊条目详细信息的期刊条目详情屏幕。在本节中,你将在故事板中添加一个新的表格视图控制器场景来表示期刊条目详情屏幕。请按照以下步骤操作:

  1. 在项目导航器中单击故事板文件,然后单击按钮。

  2. 在过滤器字段中输入table,并将表格视图控制器对象拖动到故事板中地图场景旁边:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_01.png

图 12.1:显示表格视图控制器对象的库

这将代表期刊条目详情屏幕。

  1. 验证是否已添加表格视图控制器场景:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_02.png

图 12.2:编辑区域显示地图场景旁边的表格视图控制器场景

注意,它已经包含了一个表格视图,因此您不需要在场景中添加表格视图,就像您在上一章中所做的那样。

  1. 要在期刊列表屏幕中的表格视图单元格被点击时显示期刊条目详情屏幕,请从期刊场景下的文档大纲中的journalCell(在journalCell中)拖动到表格视图控制器场景,以在它们之间添加导航:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_03.png

图 12.3:显示期刊单元的文档大纲

  1. 在弹出菜单中,在选择导航下选择显示

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_04.png

图 12.4:弹出菜单中已选择显示

这样,当期刊列表屏幕中的单元格被点击时,期刊条目详情屏幕将从右侧滑入。

  1. 验证两个场景之间是否出现了导航:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_05.png

图 12.5:编辑区域显示期刊场景和表格视图控制器场景之间的导航

您可以在故事板中重新排列场景,以便更容易看到导航。

  1. 期刊条目详情屏幕将始终显示固定数量的单元格。在文档大纲中,点击表格视图控制器场景下的表格视图

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_06.png

图 12.6:在文档大纲中选中表格视图

  1. 点击属性检查器按钮,将内容设置为静态单元格,以便使期刊条目详情屏幕显示固定数量的单元格。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_07.png

图 12.7:内容设置为静态单元格的属性检查器

  1. 构建并运行您的应用程序。在期刊列表屏幕中点击一个单元格以显示期刊条目详情屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_08.png

图 12.8:模拟器显示期刊条目详情屏幕

  1. 点击**< 期刊**按钮返回到期刊列表屏幕。

您已成功实现了期刊条目详情屏幕!太棒了!

如果您的应用程序需要在列表中显示项目的详细信息,可以使用此方法。例如,这是您 iPhone 上的联系人应用程序和设置应用程序的示例。

在下一节中,您将使地图屏幕显示地图。

实现地图屏幕

当您启动应用程序时,将显示期刊列表屏幕。在标签栏中轻触地图按钮将使地图屏幕出现,但它为空。要使地图屏幕显示地图,您需要在地图屏幕的视图控制器场景中添加一个地图视图。按照以下步骤操作:

  1. 在编辑区域中选择地图屏幕的视图控制器场景,这将展开文档大纲中的相应地图场景

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_09.png

图 12.9:编辑区域显示地图场景的视图控制器场景

  1. 要使此场景显示地图,请点击库按钮,并在过滤器字段中输入map。一个地图工具包视图对象作为结果之一出现。将其拖动到视图控制器场景中的视图中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_10.png

图 12.10:选中地图工具包视图对象的库

  1. 要使地图视图填充整个屏幕,请确认它已被选中,然后点击添加新约束按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_11.png

图 12.11:已选择地图视图的视图控制器场景

  1. 在所有最近邻居间距字段中输入0,并确保选择了浅红色支撑(它们将变为鲜红色)。点击添加 4 个约束按钮:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_12.png

图 12.12:自动布局添加新约束弹出对话框

  1. 确认地图视图填充了整个屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_13.png

图 12.13:地图视图填充屏幕的视图控制器场景

  1. 构建并运行你的应用。点击地图按钮。你应该看到与这里显示的类似地图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_14.png

图 12.14:模拟器显示地图屏幕

  1. 确认所有为JRNL应用所需的屏幕已在故事板文件中创建:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_12_15.png

图 12.15:编辑区域显示 Main.storyboard 中的所有场景

  1. 确认当你在模拟器中运行你的应用时,所有屏幕都显示得如预期。

太棒了!你现在已经完成了应用的基本用户界面!

摘要

在本章中,你完成了应用的基本结构。你为表示日记条目详情屏幕添加了一个新的表格视图控制器场景,为该屏幕配置了一个静态单元格的表格视图,并实现了当在日记列表屏幕中点击单元格时将显示此屏幕的转场。你还为地图屏幕的视图控制器场景添加了地图视图,当点击地图按钮时,它现在会显示地图。

你已成功实现了应用所需的全部屏幕,当你运行模拟器中的应用时,你将能够测试应用流程。你也应该更加熟练地使用 Interface Builder。熟悉从库中使用和定位对象对于你构建自己的应用的用户界面将至关重要。

在下一章中,你将修改日记列表屏幕、添加新日记条目屏幕和日记条目详情屏幕上的单元格,以便它们与在应用游览中显示的设计相匹配。

加入我们的 Discord!

与其他用户、专家以及作者本人一起阅读这本书。提出问题,为其他读者提供解决方案,通过 Ask Me Anything 会话与作者聊天,等等。扫描二维码或访问链接加入社区。

packt.link/ios-Swift

https://packt.link/ios-Swift

第十三章:修改应用程序屏幕

第十一章构建你的用户界面中,你添加了一些应用程序所需屏幕,以匹配应用程序浏览中显示的内容。在第十二章完成你的用户界面中,你添加了应用程序所需的其他屏幕。现在,当你运行模拟器中的应用程序时,你将能够导航到应用程序的所有屏幕,但屏幕仍然缺少数据输入和数据显示所需的用户界面元素。

在本章中,你将向期刊列表、添加新期刊条目和期刊条目详情屏幕添加和配置缺失的用户界面元素,以匹配应用程序浏览中的设计。

对于期刊列表屏幕,你需要通过向其中添加一个图像视图和两个标签来修改journalCell表格视图单元格,以便它可以显示期刊条目的照片、日期和标题。对于添加新期刊条目屏幕,你需要通过添加自定义视图、开关、文本字段、文本视图和图像视图来修改它,以便你可以输入新期刊条目的详细信息。你还需要配置图像视图以显示默认图像。对于期刊条目详情屏幕,你需要在其中添加文本视图、标签和图像视图,并配置图像视图以显示默认图像,以便屏幕可以显示现有期刊条目的详细信息。在所有用户界面元素就位后,你的应用程序将准备好进行代码实现,这些代码将在本书的第三部分中实现。

到本章结束时,你将更加熟练地添加和定位用户界面元素,并将获得更多使用约束来确定它们相对位置的经验。这将有助于确保与不同屏幕尺寸和方向的兼容性,使你能够轻松地原型化应用程序的外观和流程。

本章将涵盖以下主题:

  • 修改期刊列表屏幕

  • 修改添加新期刊条目屏幕

  • 修改期刊条目详情屏幕

技术要求

你将继续在上一章中修改的JRNL项目中工作。

本章完成的 Xcode 项目位于本书代码包的Chapter13文件夹中,可以通过以下链接下载:

github.com/PacktPublishing/iOS-17-Programming-for-Beginners-Eighth-Edition

查看以下视频以查看代码的实际效果:

youtu.be/tgo2dT1LZeM

让我们从修改期刊列表屏幕上的journalCell表格视图单元格开始。在下一节中,你将添加一些用户界面元素,使其与应用程序浏览中显示的表格视图单元格相匹配。

修改期刊列表屏幕

让我们看看应用程序浏览中期刊列表屏幕的样子:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_01.png

图 13.1:完成的 JRNL 应用程序的期刊列表屏幕

如您所见,日记列表屏幕上的表视图单元格有一个照片、一个日期和一个日记条目标题。在 第十一章构建您的用户界面 中,您为 journalCell 表视图单元格设置了青色背景,并配置了表视图以显示 10 个单元格的列。现在您将移除背景颜色,并将用户界面元素添加到 journalCell 表视图单元格中,以匹配应用导览中的设计。您将从下一节开始向其中添加图像视图。

添加到 journalCell 的图像视图

图像视图是 UIImageView 类的一个实例。它可以在您的应用中显示单个图像或一系列动画图像。要将图像视图添加到 journalCell 表视图单元格中,请按照以下步骤操作:

  1. 为了在将用户界面元素添加到故事板时更容易看到它们,从 编辑器 菜单中选择 画布 | 边界矩形

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_02.png

图 13.2:选择 Canvas | Bounds Rectangles 的编辑器菜单

这将应用一个薄薄的蓝色轮廓到故事板中的用户界面元素。

  1. 在项目导航器中点击 主故事板文件。在第一个 日记场景 下,在文档大纲中选择 journalCell

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_03.png

图 13.3:显示 journalCell 的文档大纲

  1. 在添加图像视图之前,您需要移除之前设置的背景颜色。在属性检查器中,在 视图 下,将 背景 设置为 默认

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_04.png

图 13.4:journalCell 的属性检查器设置

  1. 要将图像视图添加到表视图单元格中,点击 按钮。在过滤器字段中输入 imag。一个 图像视图 对象将出现在结果中。将其拖动到原型单元格中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_05.png

图 13.5:添加了图像视图的原型单元格

  1. 为了确保新添加的图像视图的约束可以正确设置,请验证它是否是 journalCell 表视图单元格的 内容视图 的子视图,并且已被选中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_06.png

图 13.6:选择 Image View 对象的文档大纲

  1. 点击 添加新约束 按钮,并输入以下值以设置新添加的图像视图的约束:

    • 顶部:0

    • 左侧:0

    • 底部:0

    • 宽度:90

    • 高度:90

完成后,点击 添加 5 个约束 按钮。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_07.png

图 13.7:添加新约束对话框

这将图像视图的顶部、左侧和底部边缘绑定到 journalCell 表视图单元格的相应边缘,并将其宽度和高度设置为 90 点。它还隐式地将表视图单元格的高度设置为 90 点。

  1. 在属性检查器中,在 图像视图 下,将 图像 设置为 face.smiling

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_08.png

图 13.8:将 Image 设置为 face.smiling 的图像视图

您已成功将图像视图添加到表视图单元格中,设置了其默认图像,并应用了约束以确定其相对于封装视图的位置。太酷了!

在下一节中,您将添加用于显示日记条目日期和标题的用户界面元素。

向 journalCell 添加标签

您将使用标签在 journalCell 表视图单元格中显示日期和日记条目标题。标签是 UILabel 类的实例。它可以在您的应用中显示一行或多行文本。

要向 journalCell 表视图单元格添加标签,请按照以下步骤操作:

  1. 首先,您将添加一个标签来显示日期。点击库按钮,并将一个 标签 对象拖动到您刚刚添加的图像视图和原型单元格右侧之间的空间:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_09.png

图 13.9: 选择标签对象的库

注意,标签 出现在文档大纲中,并且是 journalCell 表视图单元格的 内容视图 的子视图。

  1. 在属性检查器中,在 标签 下,使用 字体 菜单将 字体 设置为 标题 1

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_10.png

图 13.10: 标签属性检查器

  1. 点击 添加新约束 按钮,并输入以下值以设置标签的约束:

    • 顶部: 0

    • 左侧: 8

    • 右侧: 0

约束到边距 应已选中,这会将标准边距 8 点设置为标签顶部和右侧以及表格视图单元格顶部和右侧之间的空间。完成时,点击 添加 3 个约束 按钮。

  1. 验证标签的位置是否如图下截图所示,以及新添加的约束是否在文档大纲中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_11.png

图 13.11: 应用了约束的标签

标签顶部边缘与 journalCell 内容视图顶部边缘之间的空间设置为 0 + 8 点。标签左侧边缘与图像视图右侧边缘之间的空间为 8 点。标签右侧边缘与 journalCell 内容视图右侧边缘之间的空间为 0 + 8 点。标签底部边缘的位置由您之前设置的文本样式自动设置。

接下来,您将添加一个标签来显示日记条目标题。按照以下步骤操作:

  1. 点击库按钮,并将一个 标签 对象拖动到您刚刚添加的标签和原型单元格底部的空间之间:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_12.png

图 13.12: 选择标签对象的库

注意,标签 出现在文档大纲中,并且是 journalCell 表视图单元格的 内容视图 的子视图。

  1. 在属性检查器中,在 标签 下,使用 字体 菜单将 字体 设置为 正文,并将 线条 设置为 2

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_13.png

图 13.13: 标签属性检查器

线条 设置为 2 将使标签在应用运行时显示最多两行文本。

  1. 点击 添加新约束 按钮,并输入以下值以设置标签的约束:

    • 顶部: 0

    • 左侧: 8

    • 右侧: 0

约束到边距应该已经勾选,这会将标准边距8点设置为标签右侧和表格视图单元格右侧之间的空间。完成时,点击添加 3 个约束按钮。

  1. 确认标签的位置如图下截图所示,并且新添加的约束在文档轮廓中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_14.png

图 13.14:应用约束的标签

标签顶部边缘和之前添加的标签底部边缘之间的空间设置为0点。标签左侧边缘和图像视图右侧边缘之间的空间是8点。标签右侧边缘和journalCell内容视图右侧边缘之间的空间是0 + 8点。标签底部边缘的位置由你之前设置的文本样式和行数自动设置。

你可以在文档轮廓中点击一个约束,并在大小检查器中修改它。

  1. 构建并运行你的应用:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_15.png

图 13.15:模拟器显示的完成后的 journalCell 表格视图单元格

你已成功添加并配置了标签以显示journalCell表格视图单元格的日期和日记条目标题,并且已添加所有必要的约束。正如你所见,日记列表屏幕现在具有在应用浏览中显示数据所需的所有用户界面元素。太棒了!

在下一节中,你将在“添加新日记条目”屏幕中添加包含用户界面元素的堆叠视图。

修改“添加新日记条目”屏幕

让我们看看在应用浏览中“添加新日记条目”屏幕看起来是什么样子:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_16.png

图 13.16:完成后的日记应用“添加新日记条目”屏幕

苹果提供了一整套用户界面元素库,你可以在自己的应用中使用。这有助于使所有 iOS 应用具有一致的外观和感觉。正如你所见,添加新日记条目屏幕具有以下元素:

  • 一个显示星级评分的自定义视图

  • 一个开关,允许你获取当前位置

  • 一个用于日记条目标题的文本字段

  • 一个用于日记条目主体的文本视图

  • 一个用于用手机相机拍照的图片视图

现在,你将修改屏幕以匹配应用浏览中的设计,从下一节开始,通过添加一个允许用户设置星级评分的自定义视图。

将自定义视图添加到新条目场景

正如你在应用浏览中所见,添加新日记条目屏幕有一个显示星级评分的自定义视图。这个自定义视图是水平堆叠视图的子类。你将在本章中添加水平堆叠视图,并在第十九章开始使用自定义视图中完成自定义视图的实现。

栈视图是UIStackView类的一个实例。它允许你轻松地在一列或一行中排列一组视图。要将栈视图添加到“添加新日志条目”屏幕,请按照以下步骤操作:

  1. 故事板文件中,点击文档大纲中的新条目场景

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_17.png

图 13.17:编辑区域显示新条目场景

  1. 要向场景添加水平栈视图,请点击库按钮。在过滤器字段中输入hori。一个水平栈视图对象将出现在结果中。将其拖动到新条目场景的视图中:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_18.png

图 13.18:选择水平栈视图对象的库

注意,你刚刚添加的栈视图出现在文档大纲中,并且是新条目场景视图的子视图。

  1. 点击属性检查器。在栈视图下,如果尚未设置,将间距设置为8,在视图下,将背景设置为系统青色

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_19.png

图 13.19:属性检查器显示间距和背景设置

间距值决定了栈视图中元素之间的间距。

  1. 点击大小检查器。在视图下,将宽度设置为252,将高度设置为44

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_20.png

图 13.20:大小检查器显示栈视图的大小

显示星级评分的自定义视图将包含五个按钮。每个按钮的高度为44点,宽度为44点,按钮之间的间距为8点。自定义视图的总宽度将是5 x 44 + 4 x 8,总宽度为252点。

  1. 点击添加新约束按钮,并输入以下值以设置栈视图的约束:

    • 宽度:252

    • 高度:44

完成后,点击添加 2 个约束按钮。

在大小检查器中设置 UI 元素的尺寸,使你稍后添加约束更容易,因为预期的值已经设置在添加新约束对话框中。

  1. 你会看到栈视图被红色勾勒出来。点击文档大纲中的小红粉箭头。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_21.png

图 13.21:文档大纲中的箭头

  1. 你将在文档大纲中看到两个缺少约束错误:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_22.png

图 13.22:显示缺少约束错误的错误信息

由于缺少约束错误,栈视图被红色勾勒出来。这意味着栈视图相对于其封装视图的位置目前是不明确的。你将在稍后将其嵌入另一个栈视图时修复这个问题。

  1. 点击**< 结构**按钮返回。

你已成功将水平栈视图添加到新条目场景。在下一节中,你将向其中添加一个 UI 元素,允许你获取当前位置。

向新条目场景添加开关

如应用程序导游所示,在创建新日记条目时,您可以通过切换开关来获取您的当前位置。开关是UISwitch类的实例。它显示一个提供二元选择的控件,例如开/关。您还将添加一个标签来描述开关的功能,并将这两个对象放入水平堆叠视图中。

按照以下步骤操作:

  1. 要将开关添加到新条目场景,单击库按钮。在过滤器字段中输入swi。结果中会出现一个开关对象。将其拖动到水平堆叠视图下的新条目场景视图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_23.png

图 13.23:选择开关对象的库

注意,您刚刚添加的开关出现在文档大纲中,并且是新条目场景视图的子视图。

  1. 要在开关旁边添加标签,从库中拖动一个标签对象并将其放置在开关旁边:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_24.png

图 13.24:选择标签对象的库

注意,您刚刚添加的标签出现在文档大纲中,并且也是新条目场景视图的子视图。

将出现蓝色线条以帮助您将标签放置在开关的正确距离处。

  1. 双击标签并更改标签文本为“获取位置”:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_25.png

图 13.25:标签文本更改为“获取位置”

  1. 您将把标签和开关都嵌入到水平堆叠视图中。在文档大纲中,按住Shift键,单击开关,然后单击获取位置以选择开关和标签:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_26.png

图 13.26:显示已选择标签和开关的文档大纲

  1. 编辑器菜单中选择嵌入|堆叠视图

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_27.png

图 13.27:选择“嵌入”|“堆叠视图”的编辑器菜单

开关和标签现在都嵌入在堆叠视图中。

  1. 单击属性检查器按钮,在堆叠视图下将间距设置为8

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_28.png

图 13.28:设置间距为 8 的属性检查器

您已成功将包含开关和标签的水平堆叠视图添加到新条目场景。在下一节中,您将向其中添加 UI 元素,以便用户可以输入日记标题和正文。

将文本字段和文本视图添加到新条目场景

如应用程序导游所示,用户将使用此屏幕输入日记条目的标题和正文文本。要输入文本,您可以使用文本字段或文本视图。文本字段是UITextField类的实例。它显示一个可编辑的文本区域,通常限制为单行。您将使用文本字段输入日记条目的标题。文本视图是UITextView类的实例。它也显示一个可编辑的文本区域,但通常显示多行。您将使用文本视图输入日记条目的正文。

要将文本字段和文本视图添加到新条目场景,请按照以下步骤操作:

  1. 要将文本字段添加到场景中,点击库按钮。在过滤器字段中输入text。一个文本字段对象将出现在结果中。将其拖动到包含开关和标签的水平堆叠视图下的新条目场景视图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_29.png

图 13.29:选择文本字段对象的库

注意您刚刚添加的文本字段出现在文档大纲中,并且是新条目场景视图的子视图。

  1. 在属性检查器中,在文本字段下,将占位符设置为Journal Title

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_30.png

图 13.30:将占位符设置为期刊标题的属性检查器

  1. 要将文本视图添加到场景中,点击库按钮。在过滤器字段中输入text。一个文本视图对象将出现在结果中。将其拖动到您刚刚添加的文本字段下的新条目场景视图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_31.png

图 13.31:选择文本字段对象的库

验证您刚刚添加的文本视图是否出现在文档大纲中,并且是新条目场景视图的子视图。如果您愿意,也可以更改默认文本。

  1. 您将使用约束将文本视图的高度设置为默认值 128 点。选择文本视图后,点击添加新约束按钮并勾选高度约束。然后,点击添加 1 个约束按钮。文本视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_32.png

图 13.32:应用了约束的文本视图

注意文本视图周围的红色轮廓,因为它相对于封装视图的位置不明确。现在不用担心,因为您稍后会修复它。

您已成功将文本字段和文本视图添加到新条目场景。在下一节中,您将添加一个 UI 元素,允许用户在其中拍照并显示。

向新条目场景添加图像视图

如应用导览所示,用户可以使用设备相机为日记条目拍照。选定的照片将使用图像视图在添加新日记条目屏幕上显示。在第十一章构建您的用户界面中,您已将图像视图添加到journalCell表格视图单元格。现在,您将向新条目场景添加图像视图。按照以下步骤操作:

  1. 要将图像视图添加到场景中,点击库按钮。在过滤器字段中输入imag。一个图像视图对象将出现在结果中。将其拖动到文本视图下的新条目场景视图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_33.png

图 13.33:选择图像视图对象的库

注意您刚刚添加的图像视图出现在文档大纲中,并且是新条目场景视图的子视图。

  1. 点击大小检查器按钮。在视图下,将宽度高度都设置为200

  2. 点击属性检查器按钮。在图像视图下,将图像设置为face.smiling

  3. 你将使用约束来设置图像视图的宽度和高度。点击 添加新约束 按钮,勾选 宽度高度 约束(它们的值应已设置为 200)。之后,点击 添加 2 个约束 按钮。图像视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_34.png

图 13.34:应用约束的图像视图

注意图像视图周围的红色轮廓,因为它相对于包含视图的位置是不明确的。现在不用担心,因为稍后你会修复它。

新条目场景的所有用户界面元素都已添加。在下一节中,你将把它们全部嵌入到垂直堆叠视图中以解决定位问题。

在堆叠视图中嵌入用户界面元素

新条目 场现在拥有了所有必需的用户界面元素,但元素相对于包含视图的位置是不明确的。你将把所有元素嵌入到垂直堆叠视图中,并使用约束来解决定位问题。按照以下步骤操作:

  1. 在文档大纲中,选择你之前添加的所有用户界面元素,如图所示:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_35.png

图 13.35:显示所选元素的文档大纲

  1. 编辑器 菜单中选择 嵌入 | 堆叠视图

  2. 在文档大纲中选择 堆叠视图。在属性检查器中,在 堆叠视图 下,将 对齐 设置为 居中,并将 间距 设置为 8

  3. 选择堆叠视图后,点击添加新约束按钮。输入以下值以设置堆叠视图的约束:

    • 顶部:20

    • 左侧:20

    • 右侧:20

约束到边距 应已勾选,这设置了标准的 8 点边距。完成后,点击 添加 3 个约束 按钮。堆叠视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_36.png

图 13.36:应用约束后的堆叠视图

注意所有红色线条都已消失。堆叠视图与包含视图顶部、右侧和左侧边缘之间的空间已设置为 20 + 8 点。堆叠视图底部边缘的位置将自动由它包含的所有元素的高度推导出来。

  1. 你会看到文本字段没有扩展到堆叠视图的全宽。为了修复这个问题,在文档大纲中选择 日记标题,然后点击添加新约束按钮。将右侧约束设置为 8,然后点击 添加 1 个约束 按钮。文本字段将扩展到几乎堆叠视图的全宽:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_37.png

图 13.37:应用约束后的堆叠视图中的文本字段

  1. 注意文本视图也没有扩展到堆叠视图的全宽。在文档大纲中选择 文本视图,然后点击添加新约束按钮。将右侧约束设置为 8,然后点击 添加 1 个约束 按钮。文本视图将扩展到几乎堆叠视图的全宽:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_38.png

图 13.38:应用约束的堆叠视图中的文本视图

所有定位问题现在都已解决。

  1. 构建并运行您的应用,然后轻触 + 按钮以显示添加新日记条目屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_39.png

图 13.39:模拟器显示添加新日记条目屏幕

您已将所有必需的用户界面元素和约束添加到添加新日记条目屏幕。干得好!在下一节中,您将配置静态表格视图并向日记条目详情屏幕添加用户界面元素。

修改日记条目详情屏幕

让我们看看日记条目详情屏幕在应用导览中的样子:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_40.png

图 13.40:完成后的日记应用日记条目详情屏幕

向上滚动可显示日记条目详情屏幕的剩余部分:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_41.png

图 13.41:日记条目详情屏幕的其余部分

如您所见,日记条目详情屏幕具有以下元素:

  • 显示日期的标签

  • 显示星级评分的自定义视图

  • 日记条目标题的标签

  • 日记条目正文的标签

  • 一个用于您用手机相机拍摄的照片的图像视图

  • 显示地图位置的图像视图

此外,您还需要滚动以查看整个屏幕。您现在将修改它以匹配应用导览中的设计,首先在下一节中设置表格视图单元格的数量和大小。

配置静态表格视图单元格的数量和大小

第十二章完成您的用户界面 中,您已将表格视图控制器场景添加到 Main 故事板文件,并配置它使用固定数量的单元格。这将代表日记条目详情屏幕。现在,您将设置单元格的数量和大小以匹配应用导览中的布局。按照以下步骤操作:

  1. Main 故事板文件中,在文档大纲中单击 Table View Controller Scene,然后单击 Navigation Item

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_42.png

图 13.42:选择 Table View Controller Scene 的文档大纲

  1. 单击属性检查器按钮,在 Navigation Item 下,将 Title 设置为 Entry Detail

  2. 在文档大纲中选择 Table View Section

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_43.png

图 13.43:选择 Table View Section 的文档大纲

  1. Table View Section 下的属性检查器中,将 Rows 设置为 7

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_44.png

图 13.44:属性检查器显示 7 行

  1. 在文档大纲中的 Table View Section 下单击第二个 Table View Cell

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_45.png

图 13.45:显示第二个 Table View Cell 的文档大纲

  1. 单击大小检查器按钮。在 Table View Cell 下,将 Row Height 设置为 60

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_46.png

图 13.46:大小检查器显示行高设置为 60

  1. 在文档大纲中点击表格视图部分下的第四个表格视图单元格。在大小检查器中,在表格视图单元格下,将行高设置为150

  2. 在文档大纲中点击表格视图部分下的第五个表格视图单元格。在大小检查器中,在表格视图单元格下,将行高设置为316

  3. 重复之前的步骤,对第六个表格视图单元格进行操作。

你已经设置了单元格的数量和大小以匹配应用导览中显示的布局。在下一节中,你将为每个单元格添加用户界面元素。

向静态表格视图单元格添加用户界面元素

在上一节中,你使用了一个堆叠视图来帮助你管理添加新日志条目屏幕中的多个用户界面元素。这里,你将使用带有静态表格视图单元格的表格视图。使用静态表格视图的优势在于内置了视图滚动,因此它可以容纳比设备屏幕更高的视图。按照以下步骤操作:

  1. 在文档大纲中点击第一个表格视图单元格,并从库中将一个标签对象拖入其中。在属性检查器中,在标签下,使用字体菜单将样式设置为Semibold并将对齐方式设置为右对齐。

  2. 点击添加新约束按钮,并将标签的顶部、左侧和右侧约束设置为0。确保勾选了约束到边距,然后点击添加 3 个约束按钮。标签现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_47.png

图 13.47:应用了约束的标签

  1. 在文档大纲中点击第二个表格视图单元格,并从库中将一个水平堆叠视图对象拖入其中。在属性检查器中,在堆叠视图下,将间距设置为8。在视图下,将背景设置为系统青色颜色

  2. 点击大小检查器。在视图下,将宽度设置为252高度设置为44

  3. 点击添加新约束按钮,并设置宽度高度约束(它们的值应该已经存在)。点击添加 2 个约束按钮。

  4. 点击对齐按钮,勾选容器内水平对齐容器内垂直对齐。点击添加 2 个约束按钮。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_48.png

图 13.48:对齐按钮和对话框

  1. 堆叠视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_49.png

图 13.49:应用了约束的堆叠视图

  1. 点击第三个表格视图单元格,并从库中将一个标签对象拖入其中。在属性检查器中,在标签下,将样式设置为Semibold并将对齐方式设置为左对齐。

  2. 点击添加新约束按钮,并将标签的顶部、左侧和右侧约束设置为0。确保勾选了约束到边距,然后点击添加 3 个约束按钮。标签现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_50.png

图 13.50:应用了约束的标签

  1. 点击第四个表格视图单元格,从库中将一个文本视图对象拖入其中。在属性检查器中,在文本视图下取消选中可编辑可选择。如果您愿意,也可以更改默认文本。

  2. 点击“添加新约束”按钮,并将顶部、左侧、右侧和底部约束设置为0。确保勾选约束到边距,并点击“添加 4 个约束”按钮。文本视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_51.png

图 13.51:应用约束的文本视图

  1. 点击第五个表格视图单元格,从库中将一个图像视图对象拖入其中。在属性检查器中,在图像视图下将图像设置为face.smiling

  2. 在大小检查器中,在视图下将宽度高度设置为300

  3. 点击“添加新约束”按钮,并设置宽度高度约束(它们的值应该已经存在)。点击“添加 2 个约束”按钮。

  4. 点击对齐按钮,勾选在容器中水平对齐在容器中垂直对齐。点击“添加 2 个约束”按钮。图像视图现在将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_52.png

图 13.52:应用约束的图像视图对象

  1. 点击第六个表格视图单元格,从库中将一个图像视图对象拖入其中。在属性检查器中,在图像视图下将图像设置为map

  2. 对此图像视图重复步骤 1315。现在它将看起来像以下截图:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_53.png

图 13.53:应用约束的图像视图对象

  1. 所有必需的用户界面元素都已添加到条目详情场景中。构建并运行您的应用,并在“日记列表”屏幕上点击一行以导航到“日记条目详情”屏幕:

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_54.png

图 13.54:模拟器显示“日记条目详情”屏幕

  1. 滚动查看“日记条目详情”屏幕的剩余部分。

https://github.com/OpenDocCN/freelearn-mobi-zh/raw/master/docs/ios18-prog-bgn/img/B31371_13_55.png

图 13.55:模拟器显示“日记条目详情”屏幕的剩余部分

太棒了!您已经修改了应用的所有屏幕,并准备好在本书的下一部分中添加它们的功能。

摘要

在本章中,您修改了“日记列表”、“添加新日记条目”和“日记条目详情”屏幕,以匹配应用导览中显示的设计。对于“日记列表”屏幕,您通过添加图像视图和两个标签来修改journalCell表格视图单元格。您通过添加自定义视图、开关、文本字段、文本视图和图像视图来修改“添加新日记条目”屏幕,并配置图像视图以显示默认图像。对于“日记条目详情”屏幕,您添加了文本视图、标签和图像视图,并配置图像视图以显示默认图像。

您现在在如何使用 Interface Builder 添加和配置多个用户界面元素、使用大小检查器设置它们的大小和位置以及使用添加新约束和对齐按钮应用必要的约束方面有了更多的经验。这将有助于确保与不同屏幕尺寸和方向的兼容性。您还应该能够轻松地原型化您应用的界面和流程。

您现在已经完成了故事板和设计设置。您可以浏览应用中的每一个屏幕,看看它们的样子,尽管这些屏幕中没有任何实际数据。如果这个应用就像一座正在建造的房子,那么您就像是已经建好了所有的墙壁和地板,房子现在可以开始内部装修了。干得好!

这本书的第二部分到此结束。在下一部分,你将开始输入使你的应用工作的所有必需代码。在下一章,你将开始学习更多关于模型-视图-控制器设计模式的知识。你还将了解表格视图是如何工作的,这对于理解“期刊列表”屏幕的工作方式至关重要。

留下您的评价!

感谢您从 Packt Publishing 购买这本书——我们希望您喜欢它!您的反馈对我们来说是无价的,它帮助我们改进和成长。一旦您阅读完毕,请花一点时间在亚马逊上留下评价;这只需要一分钟,但对于像您这样的读者来说意义重大。扫描下面的二维码或访问链接,以获得您选择的免费电子书。

packt.link/NzOWQ

https://packt.link/NzOWQ

考虑柔性负荷的综合能源系统低碳经济优化调度【考虑碳交易机制】(Matlab代码实现)内容概要:本文围绕“考虑柔性负荷的综合能源系统低碳经济优化调度”展开,重点研究在碳交易机制下如何实现综合能源系统的低碳化与经济性协同优化。通过构建包含风电、光伏、储能、柔性负荷等多种能源形式的系统模型,结合碳交易成本与能源调度成本,提出优化调度策略,以降低碳排放并提升系统运行经济性。文中采用Matlab进行仿真代码实现,验证了所提模型在平衡能源供需、平抑可再生能源波动、引导柔性负荷参与调度等方面的有效性,为低碳能源系统的设计与运行提供了技术支撑。; 适合人群:具备一定电力系统、能源系统背景,熟悉Matlab编程,从事能源优化、低碳调度、综合能源系统等相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①研究碳交易机制对综合能源系统调度决策的影响;②实现柔性负荷在削峰填谷、促进可再生能源消纳中的作用;③掌握基于Matlab的能源系统建模与优化求解方法;④为实际综合能源项目提供低碳经济调度方案参考。; 阅读建议:建议读者结合Matlab代码深入理解模型构建与求解过程,重点关注目标函数设计、约束条件设置及碳交易成本的量化方式,可进一步扩展至多能互补、需求响应等场景进行次开发与仿真验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值