译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎重视大众号 「Swift花园」
喜欢文章?不如来个 ➕三连?重视专栏,重视我

用 Core Data 创立图书

项目中的第一个任务b V l k是为咱们的图书设计 Core Data 模型,然后创立一个把书增加到数D % p t –据库的新视图。

首先是模型:翻开 Bookworm.xcdatamodeld ,然后增加一个新的实体,取名 “Book” —— 咱们将为用户读过的每本书创立一个新的目标。以构成书的要素,我需要增加下面这样特点:% E e –

  • id, UUID —— 一个能够保证仅有的用于区分不同图书的标识符
  • title, String —— 书名
  • author, String —— 书的作者
  • genre, Stris ) p ] 5 e d q /ng —— 门户
  • review, String —— 用户关于书的评价
  • rating, Integer 16 —— 用户给这本书的打分

一切这些特点看起来~ o + 9 G b都挺合理,但最终一项 “integer 16” 是什么意思?16 代表什么?Integer 32 和 Integer 64 又是什么? 是这样的,正如FloatDouble的区别:Integer 1L G |6 运用 16 个二进制位 (“bits”) 来存储数字,所以它能够保存从 -32,768 到 32,767 的数字,而k 0 o s q i E @ 5 Integer 32 运用 32 位来存储数字S b j Q d 3 8 8 g,所以能保存从 -2,147,483,648 到 2,147,4V q q { +83,647 的数字。至于 In{ c ! q 3 N Mteger 64… 那是相当大的数。

要点在于这些Q D x . ! f j }类型之间不是可交换的S U H & / a o H:你不能接纳一个 64 位的数字,测验存放在 16 位的数字中,这样会丢掉精度。另一方面,用 64 位整数来存放一个咱们已知很小的数字也是很浪费的。因此,Cs O , fore Data 供给了不同的选项,让咱们选取想要运用的存储空间。

下一步是写一个用来创立新实体的表单。这个进程结合了许多你现已学习过的技能:Form@State@EnvirV W S D %onmentTextFieldPickersheet(),等等,加上你刚学的 Core Data 知识。

咱们创立一个新的叫 “AddBookView” 的 SwiftUI 视图。关于它的特点,咱们需要用到一个环境特点,存储 managed object context:

@Environment(.managedObjectContext) var moc7 h V 7 :

为了构成一本书,除了 id 之外,咱们需要为书的每个字段运用@State特点,而 id 咱们能够自动生成:

@Sb Z e  B @tate private var title = ""
@State privz u Y . J B bate var author = ""
@State private var rating = 3
@State private var genre = ""
@State prii 0 ? 2 9 % r d ;vate var revil = [ 2 Y Z 7 G ~ew = ""

最终,咱们还需要一L p d B z个额定的特点存储一切可能~ 8 ( 0 k 6 8 1 Y的门户,以便结合ForEach完结一个选择器。把下面这个特点增加到An k i ZddBookView

lets : _ genL s + w 6 F Yres = ["Fa) L x hntasy", "Horror", "Kids", "Mystery",L ) X , g ? "Poetry", "Romance", "Thriller"0 G G i X _ J]

关于表单的完结暂时到这儿 —— 咱们稍后还会改进它,不过目前为止现已够用了。把body替换成下面这样:3 ? h

Navi7 y 2gationView {
FormH n z O X {
Section {
Te& l - R e q ^xtField("Name of book", text: $title)
TextField("/ Z m q Q 4 mAuthor's name", text: $author)
Picker("Genre", selection: $genre) {
ForEach(genres, id: .self) {6 k ~
Text($0)
}
}
}
Section {
Picker("Rating", s, 3 r * T xelection: $r0 ( ; 8 ating) {
ForEach(0..<6) {
Text("($0)")
}
}
TextField("Write a review", text: $review)
}
Section {
Button("Save") {
// add t* p ] r E f ) z ;he book
}
}
}
.navigationBarTitle("Add Book")
}

关于按钮的 action,咱们要完结创立一个Book类完结的动作,用到咱们的. z 0 R c Q _ F managed object context,从表单v P q e K 中复制一切的字段(包括把rating转换成Int16以习惯 Core Dati W T W F 8 W x Da),然后保存 managed object( ; 6 | U @ context。

大部分操作9 A m y g 7 e只是简单的复制,稍微有点模糊的当地在于咱们如何把一个Int的 rating 转换成Int16。很简单猜到:Int16(someInt)能够完结咱们的需求。

用下面的代码替换// 增加图书注释:

let newBook = Book(context: self.moc)
newBook.title = self.titlec 6 B m R
newBook.author = self.author
newBook.rating = Int16(self.rating)
newBook.genre = self.genre
newBook.review = self.review
try? self.moc.save()

这样咱们就完h p ] 1结了表单,但咱们还需要一P ) : R F $个在图书被增加后显现和隐藏它的办法。

显现新增的 AddBookView0 I ! a 1 O / d s要涉及回到 ContentView.swift ,完结下面几个针对 sheet 的常规动作:

  1. 增加一个 @Stat2 8 o @ p b K f Ce 特点盯梢 sheet 是否应该显现
  2. 增加 m A某个按钮 —— 能够是一个导航栏菜单 —— 以便触} v – $ . M B发该特点。
  3. 一个用来8 I = 1 U @ d N $显现AddBookViewsheet()modifier。

不过,这儿还有一个额定的任务,跟 SwiftUI 的 environment 作业机制相关。你看,当咱们把一个目标放进视图的环境P # 5中时,它关于视图是可拜访的,并且一切以这个视图为先人的视图也能够拜访这个目标。具体来说,假如咱们的视图 A 包括视图 B,那么视图 A 环境里的任h L v Y i p A, = E d m东西也将处于视, ? 0图 B 的环境中。

现在,让咱们来考虑一下 sheet —— 这些 iOS 上以全屏方法出现的弹出式窗口。是的,某一个屏能够触发它们显现,但这是B Q / = U F否意味着被出现的 sheet 能够称这些触j j Y V e o发它们的视图为先人呢?SwiftUI 的答案是否定的。因此,假如咱们以 sheet 的方法出现一个新视图,咱们需要显式地传递 managed object context。关于] | z `ContentView以 sheet 方式出现的AddBookView,咱们需要增加一个 managed obo @ C w 3ject context 特点到ContentView,以便能够传递给AddBookView

把下面三个! : #特点增加到ContentView

@Environment(.managedObjectContext) var moc
@FetchRequest(entity: Book.entity(), sortDescriptors: []) var books: Fetchq $ L a s uedResults<Book>
@State private var showingAddScreeO w I R nn = false

这样咱们就得到了一个能够传给AddBookView的 managed obR ^ ? 1ject context,一个读取一切图书的 fetch 恳求 (以便咱们测验一切作业正常),以及一个盯梢是否应当展现增加图书界面的布尔值。

关于ContentVie| X J L N o 2wbody,咱们将用到一个导航视图,在上面增加一个标题B 3 R m c l B g @,以及在右上角增加一个按钮,这个按钮是咱们触发增加图书 sheeF / 4 p &t 的当地。

你现已知道咱们如何利用@Environment特点包装器从环境中读取值,这儿咱们还需要学习如何往环境中写入值。这用到一个叫environment()的 modifier,它接纳两个参数:要写入的 keyPath,要写入的值。关于咱们所运用的值,直接传递即可。

ContentV8 C % , 6 W tiewbody特点替换成下面这样:

 NavigationView {
Text("Count: (books.count)")
.navix Q J 6 h IgationBarTit= D F + A Xle("BooV ? 0 2 J (kworm")
.navigatb C 2 ionBarItems(trailing: Button(action: {
self.showingAddScreen.toggle()
}) {
Image(systemName: "pE $ Q c * 1lus")
})
.sheet(isPres* B ) 3 G v ; I $ented: $showingAddScreen) {
AddBookView().environment(.managedObjectContext, self.moc)
}
}

最终一步是保证用户完结增加之后关闭表单。

咱们需要用到另一个环境特点,盯梢当时的 presentation mode:

@Environment(.pre. T d e u QsentationMode) var presentationMode

然后在按钮的; p V 8 =的 action 闭包最终y y w调用dismiss,像这样:

st H delf.presew Q P g 2 7ntationMode.wrappedValue.dismiss()

现在,你能够运转使用,测验增加一个新书,当AddBookView消失时,你应该会发现计数标签更新为 1 。

提示:取决于你的 Xcode 版别,有两个 SwiftUI 的小毛病可能会影响到你。第一个是你可能会发现 + 按钮难以点击,你需要点的十分精确。这是由于 UIKit 扩展了点击热区以方便交互,而 SwiftUI 没有。第H ? Y k F . (二点是你可能发现T N p e n _ M ( r点击按钮只响应一次。i p O T 2这肯定是一个 bug,由于咱们假如用文本视图的onTapGesture()来触发布尔值,一切作业正常 —— 只要导航z ( N ; m栏上的按钮才会这样。希望这两个 bug 能很快得到解决 —— 可能你看到这篇教程的时分现已解决了!

[SwiftUI 100 天] Bookworm-part4 用 Core Data 创建图书