Start 100DaysOfSwiftUI from 2020.Mar.18th.
Day 53, 54, 55, 56: Project 11, part one/two/three/four
done with 5 hours
New findings: followings are new findings
- AnyView
- this should be useful to show variable views which depends on the condition.
Really fun !
ContentView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
struct ContentView: View { @Environment(\.managedObjectContext) var moc @FetchRequest(entity: Book.entity(), sortDescriptors: [ NSSortDescriptor(keyPath: \Book.title, ascending: true), NSSortDescriptor(keyPath: \Book.author, ascending: true) ]) var books: FetchedResults<Book> @State private var showingAddScreen = false var body: some View { NavigationView { List { ForEach(books, id: \.self) { book in NavigationLink(destination: DetailView(book: book)) { EmojiRatingView(rating: book.rating) .font(.largeTitle) VStack(alignment: .leading) { Text(book.title ?? "Unknown Title") .font(.headline) .foregroundColor(book.rating == 1 ? Color.red : .black) Text(book.author ?? "Unknown Author") .foregroundColor(.secondary) } } } .onDelete(perform: deleteBooks) } .navigationBarTitle("Bookworm") .navigationBarItems(leading: EditButton(), trailing: Button(action: { self.showingAddScreen.toggle() }, label: { Image(systemName: "plus") })) .sheet(isPresented: $showingAddScreen, content: { AddBookView().environment(\.managedObjectContext, self.moc) }) } } func deleteBooks(at offsets:IndexSet) { for offset in offsets { let book = books[offset] moc.delete(book) } try? moc.save() } } |
AddBookView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
struct AddBookView: View { @Environment(\.presentationMode) var presentationMode @Environment(\.managedObjectContext) var moc @State private var title = "" @State private var author = "" @State private var rating = 3 @State private var genre = "" @State private var review = "" let genres = ["Fantasy", "Horror", "Kids", "Mystery", "Poetry", "Romance", "Thriller"] var body: some View { NavigationView { Form { Section { TextField("Name of book", text: $title) TextField("Author's name", text: $author) Picker("Genre", selection: $genre) { ForEach(genres, id:\.self) { Text($0) } } } Section { RatingView(rating: $rating) TextField("Write a review", text: $review) } Section { Button("Save") { let newBook = Book(context: self.moc) newBook.title = self.title newBook.author = self.author newBook.rating = Int16(self.rating) newBook.genre = self.genre newBook.review = self.review newBook.date = Date() try? self.moc.save() self.presentationMode.wrappedValue.dismiss() } } .disabled(genre == "") } .navigationBarTitle("Add Book") } } } |
RatingView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
struct RatingView: View { @Binding var rating: Int var label = "" var maximumRating = 5 var offImage: Image? var onImage = Image(systemName: "star.fill") var offColor = Color.gray var onColor = Color.yellow var body: some View { HStack { if label.isEmpty == false { Text(label) } ForEach(1..<maximumRating + 1) { number in self.image(for: number) .foregroundColor(number > self.rating ? self.offColor : self.onColor) .onTapGesture { self.rating = number } } } } func image(for number: Int) -> Image { if number > rating { return offImage ?? onImage } else { return onImage } } } |
EmpjoRatingView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
struct EmojiRatingView: View { let rating: Int16 var body: some View { switch rating { case 1: return Text("①") case 2: return Text("②") case 3: return Text("③") case 4: return Text("④") default: return Text("⑤") } } } |
DetailView.swift
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
struct DetailView: View { @Environment(\.managedObjectContext) var moc @Environment(\.presentationMode) var presentationMode @State private var showingDeleteAlert = false let book: Book var body: some View { GeometryReader { geometry in VStack { ZStack(alignment: .bottomTrailing) { Image(self.book.genre ?? "Fantasy") .frame(maxWidth: geometry.size.width) Text(self.book.genre?.uppercased() ?? "FANTASY") .font(.caption) .fontWeight(.black) .padding(8) .foregroundColor(.white) .background(Color.black.opacity(0.75)) .clipShape(Capsule()) .offset(x: -5, y: -5) } Text(self.book.author ?? "Unknown author") .font(.title) .foregroundColor(.secondary) Text(self.book.review ?? "No review") .padding() RatingView(rating: .constant(Int(self.book.rating))) .font(.largeTitle) Spacer() Text("Date: \(self.formattedDate(book: self.book))") Spacer() } } .navigationBarTitle(Text(book.title ?? "Unknown Book"), displayMode: .inline) .navigationBarItems(trailing: Button(action: { self.showingDeleteAlert = true }, label: { Image(systemName: "trash") })) .alert(isPresented: $showingDeleteAlert) { Alert(title: Text("Delete book"), message: Text("Are you sure?"), primaryButton: .destructive(Text("Delete")) { self.deleteBook() }, secondaryButton: .cancel() ) } } func deleteBook() { moc.delete(book) // uncomment this line if you want to make the deletion permanent try? self.moc.save() presentationMode.wrappedValue.dismiss() } func formattedDate(book:Book) -> String { if let date = book.date { let formatter = DateFormatter() formatter.dateStyle = .long return formatter.string(from: date) } return "N/A" } } |
Sponsor Link