Complete Challenge 3
> Use an action sheet to customize the way users are sorted in each screen – by name or by most recent.
This commit is contained in:
parent
b4b7f62067
commit
877c2acfa9
|
@ -49,6 +49,7 @@
|
|||
F326140E23CFFDE2009EC215 /* ContactsListView+ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326140D23CFFDE2009EC215 /* ContactsListView+ViewModel.swift */; };
|
||||
F326141323D1073A009EC215 /* LocalNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326141223D1073A009EC215 /* LocalNotifications.swift */; };
|
||||
F326141523D107FC009EC215 /* UserNotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326141423D107FC009EC215 /* UserNotificationsService.swift */; };
|
||||
F326141723D14EEF009EC215 /* Contact+SortingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F326141623D14EEF009EC215 /* Contact+SortingState.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -93,6 +94,7 @@
|
|||
F326140D23CFFDE2009EC215 /* ContactsListView+ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ContactsListView+ViewModel.swift"; sourceTree = "<group>"; };
|
||||
F326141223D1073A009EC215 /* LocalNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotifications.swift; sourceTree = "<group>"; };
|
||||
F326141423D107FC009EC215 /* UserNotificationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationsService.swift; sourceTree = "<group>"; };
|
||||
F326141623D14EEF009EC215 /* Contact+SortingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Contact+SortingState.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -246,6 +248,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F3203BB823C8467000265268 /* Contact+FilterState.swift */,
|
||||
F326141623D14EEF009EC215 /* Contact+SortingState.swift */,
|
||||
F3203BBB23C8579400265268 /* Contact+CoreDataClass.swift */,
|
||||
F3203BBC23C8579400265268 /* Contact+CoreDataProperties.swift */,
|
||||
F326140923CE689A009EC215 /* Contact+InitHelpers.swift */,
|
||||
|
@ -401,6 +404,7 @@
|
|||
F3203BF423CD140100265268 /* ContactQRCodeRepresentable.swift in Sources */,
|
||||
F3203BB723C844F100265268 /* ContactsListView.swift in Sources */,
|
||||
F3203BED23CC97DC00265268 /* UserQRCodeContainerView+ViewModel.swift in Sources */,
|
||||
F326141723D14EEF009EC215 /* Contact+SortingState.swift in Sources */,
|
||||
F3203BB523C841A700265268 /* ContactsContainerView+ViewModel.swift in Sources */,
|
||||
F326140A23CE689A009EC215 /* Contact+InitHelpers.swift in Sources */,
|
||||
F3203BEB23CC977000265268 /* UserQRCodeContainerView.swift in Sources */,
|
||||
|
|
|
@ -14,6 +14,7 @@ import CoreData
|
|||
extension Contact: ContactQRCodeRepresentable {
|
||||
@NSManaged public var qrCodeData: Data?
|
||||
|
||||
@NSManaged public var dateAdded: Date
|
||||
@NSManaged public var statusValue: Int16
|
||||
|
||||
var status: Status {
|
||||
|
|
|
@ -18,16 +18,31 @@ extension Contact {
|
|||
}
|
||||
|
||||
|
||||
enum SortDescriptors {
|
||||
static let `default` = [
|
||||
NSSortDescriptor(keyPath: \Contact.name, ascending: true)
|
||||
]
|
||||
public enum SortDescriptors {
|
||||
public static let byName = NSSortDescriptor(keyPath: \Contact.name, ascending: true)
|
||||
public static let byMostRecent = NSSortDescriptor(keyPath: \Contact.dateAdded, ascending: false)
|
||||
public static let byOldestAdded = NSSortDescriptor(keyPath: \Contact.dateAdded, ascending: true)
|
||||
|
||||
|
||||
public static let `default` = Self.byName
|
||||
|
||||
|
||||
public static func forSortingState(_ sortingState: Contact.SortingState) -> [NSSortDescriptor] {
|
||||
switch sortingState {
|
||||
case .byName:
|
||||
return [Self.byName]
|
||||
case .byMostRecent:
|
||||
return [Self.byMostRecent, byName]
|
||||
case .byOldestAdded:
|
||||
return [Self.byOldestAdded, byName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum Predicate {
|
||||
public enum Predicate {
|
||||
|
||||
static func contacts(for status: Status) -> NSPredicate {
|
||||
public static func contacts(for status: Contact.Status) -> NSPredicate {
|
||||
let keyword = NSComparisonPredicate.keyword(for: .equalTo)
|
||||
|
||||
return NSPredicate(
|
||||
|
@ -40,10 +55,13 @@ extension Contact {
|
|||
|
||||
|
||||
|
||||
static func fetchRequest(for filterState: FilterState) -> NSFetchRequest<Contact> {
|
||||
@nonobjc public class func fetchRequest(
|
||||
filteringOn filterState: FilterState,
|
||||
sortingBy sortingState: SortingState
|
||||
) -> NSFetchRequest<Contact> {
|
||||
let fetchRequest: NSFetchRequest<Contact> = Self.fetchRequest()
|
||||
|
||||
fetchRequest.sortDescriptors = Self.SortDescriptors.default
|
||||
fetchRequest.sortDescriptors = Self.SortDescriptors.forSortingState(sortingState)
|
||||
|
||||
if let status = filterState.contactStatus {
|
||||
fetchRequest.predicate = Self.Predicate.contacts(for: status)
|
||||
|
@ -51,5 +69,4 @@ extension Contact {
|
|||
|
||||
return fetchRequest
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import SwiftUI
|
|||
|
||||
|
||||
extension Contact {
|
||||
enum FilterState: String {
|
||||
public enum FilterState: String {
|
||||
case all
|
||||
case contacted
|
||||
case uncontacted
|
||||
|
@ -20,14 +20,14 @@ extension Contact {
|
|||
extension Contact.FilterState: CaseIterable {}
|
||||
|
||||
extension Contact.FilterState: Identifiable {
|
||||
var id: String { self.rawValue }
|
||||
public var id: String { self.rawValue }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension Contact.FilterState {
|
||||
|
||||
var displayName: String {
|
||||
public var displayName: String {
|
||||
switch self {
|
||||
case .all:
|
||||
return "All"
|
||||
|
|
|
@ -20,9 +20,11 @@ extension Contact {
|
|||
let contact = Contact(context: context)
|
||||
|
||||
contact.name = name
|
||||
contact.uuid = UUID()
|
||||
contact.status = status
|
||||
|
||||
contact.uuid = UUID()
|
||||
contact.dateAdded = Date()
|
||||
|
||||
return contact
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// Contact+SortingState.swift
|
||||
// QRConnections
|
||||
//
|
||||
// Created by CypherPoet on 1/16/20.
|
||||
// ✌️
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
|
||||
extension Contact {
|
||||
public enum SortingState: String {
|
||||
case byName
|
||||
case byMostRecent
|
||||
case byOldestAdded
|
||||
}
|
||||
}
|
||||
|
||||
extension Contact.SortingState: CaseIterable {}
|
||||
|
||||
extension Contact.SortingState: Identifiable {
|
||||
public var id: String { self.rawValue }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Computeds
|
||||
extension Contact.SortingState {
|
||||
|
||||
public var displayName: String {
|
||||
switch self {
|
||||
case .byName:
|
||||
return "By Name"
|
||||
case .byMostRecent:
|
||||
return "By Most Recent"
|
||||
case .byOldestAdded:
|
||||
return "By First Added"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
|
||||
|
||||
extension Contact {
|
||||
enum Status: Int16 {
|
||||
public enum Status: Int16 {
|
||||
case contacted
|
||||
case uncontacted
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="15702" systemVersion="19C57" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Contact" representedClassName="Contact" syncable="YES">
|
||||
<attribute name="dateAdded" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="qrCodeData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="statusValue" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
@ -12,6 +13,6 @@
|
|||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Contact" positionX="6.3984375" positionY="-108.1640625" width="128" height="103"/>
|
||||
<element name="Contact" positionX="6.3984375" positionY="-108.1640625" width="128" height="118"/>
|
||||
</elements>
|
||||
</model>
|
|
@ -25,6 +25,7 @@ extension SampleData {
|
|||
contactUUIDs.enumerated().map { (index, uuid) in
|
||||
let contact = Contact(context: context)
|
||||
|
||||
contact.dateAdded = Date()
|
||||
contact.status = Contact.Status.allCases.randomElement()!
|
||||
contact.uuid = uuid
|
||||
contact.name = "Contact \(index + 1)"
|
||||
|
|
|
@ -19,6 +19,7 @@ extension ContactsContainerView {
|
|||
|
||||
// MARK: - Published Properties
|
||||
@Published var filterState: Contact.FilterState = .all
|
||||
@Published var sortingState: Contact.SortingState = .byName
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ struct ContactsContainerView {
|
|||
@ObservedObject var viewModel = ViewModel()
|
||||
|
||||
@State private var isShowingScannerView = false
|
||||
@State private var isShowingSortingActionSheet = false
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,13 +31,17 @@ extension ContactsContainerView: View {
|
|||
contactsList
|
||||
}
|
||||
.navigationBarTitle("Collected Contacts")
|
||||
.navigationBarItems(trailing: addContactButton)
|
||||
.navigationBarItems(
|
||||
leading: changeSortingButton,
|
||||
trailing: addContactButton
|
||||
)
|
||||
.sheet(isPresented: $isShowingScannerView) {
|
||||
QRCodeScannerView(
|
||||
simulatedData: "🚀 Rocket Man",
|
||||
onScanCompleted: self.codeScanCompleted(_:)
|
||||
)
|
||||
}
|
||||
.actionSheet(isPresented: $isShowingSortingActionSheet) { self.sortingActionSheet }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -61,12 +66,26 @@ extension ContactsContainerView {
|
|||
|
||||
private var contactsList: some View {
|
||||
ContactsListView(
|
||||
viewModel: .init(filterState: viewModel.filterState),
|
||||
viewModel: .init(
|
||||
filterState: viewModel.filterState
|
||||
),
|
||||
filterState: viewModel.filterState,
|
||||
sortingState: viewModel.sortingState
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private var changeSortingButton: some View {
|
||||
Button(action: {
|
||||
self.isShowingSortingActionSheet = true
|
||||
}) {
|
||||
Image(systemName: "arrow.up.and.down")
|
||||
.imageScale(.large)
|
||||
Text("Sorting")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var addContactButton: some View {
|
||||
Button(action: {
|
||||
self.isShowingScannerView = true
|
||||
|
@ -76,6 +95,21 @@ extension ContactsContainerView {
|
|||
Text("Scan")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private var sortingActionSheet: ActionSheet {
|
||||
let actionButtons: [ActionSheet.Button] = Contact.SortingState.allCases.map { sortingState in
|
||||
.default(
|
||||
Text(sortingState.displayName),
|
||||
action: { self.viewModel.sortingState = sortingState }
|
||||
)
|
||||
}
|
||||
|
||||
return ActionSheet(
|
||||
title: Text("Sort contacts in the list"),
|
||||
buttons: actionButtons + [.cancel()]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,10 +17,17 @@ struct ContactsListView {
|
|||
|
||||
init(
|
||||
viewModel: ViewModel = .init(),
|
||||
filterState: Contact.FilterState
|
||||
filterState: Contact.FilterState,
|
||||
sortingState: Contact.SortingState
|
||||
) {
|
||||
self.viewModel = viewModel
|
||||
self.fetchRequest = FetchRequest(fetchRequest: Contact.fetchRequest(for: filterState))
|
||||
|
||||
self.fetchRequest = FetchRequest(
|
||||
fetchRequest: Contact.fetchRequest(
|
||||
filteringOn: filterState,
|
||||
sortingBy: sortingState
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +80,8 @@ struct ContactsListView_Previews: PreviewProvider {
|
|||
return Group {
|
||||
ContactsListView(
|
||||
viewModel: .init(filterState: .all),
|
||||
filterState: .all
|
||||
filterState: .all,
|
||||
sortingState: .byName
|
||||
)
|
||||
.environment(\.managedObjectContext, managedObjectContext)
|
||||
|
||||
|
|
Loading…
Reference in New Issue