译自 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 又是什么? 是这样的,正如Float
和Double
的区别: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 %onment
,TextField
,Picker
,sheet()
,等等,加上你刚学的 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 $个在图书被增加后显现和隐藏它的办法。
显现新增的 AddBookView
首0 I ! a 1 O / d s要涉及回到 ContentView.swift ,完结下面几个针对 sheet 的常规动作:
- 增加一个
@Stat2 8 o @ p b K f Ce
特点盯梢 sheet 是否应该显现 - 增加 m A某个按钮 —— 能够是一个导航栏菜单 —— 以便触} v – $ . M B发该特点。
- 一个用来8 I = 1 U @ d N $显现
AddBookView
的sheet()
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 2w
的body
,咱们将用到一个导航视图,在上面增加一个标题B 3 R m c l B g @,以及在右上角增加一个按钮,这个按钮是咱们触发增加图书 sheeF / 4 p &t 的当地。
你现已知道咱们如何利用@Environment
特点包装器从环境中读取值,这儿咱们还需要学习如何往环境中写入值。这用到一个叫environment()
的 modifier,它接纳两个参数:要写入的 keyPath,要写入的值。关于咱们所运用的值,直接传递即可。
把ContentV8 C % , 6 W tiew
的body
特点替换成下面这样:
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 能很快得到解决 —— 可能你看到这篇教程的时分现已解决了!