Using Data from Firestore Data Class in reusable picker SwiftUI - google-cloud-firestore

I feel like I'm missing something really obvious and I can't seem to figure it out. I want to use a reusable picker in SwiftUI, the one I am referring to is Stewart Lynch's "Reusable-Custom-Picker" https://github.com/StewartLynch/Reusable-Custom-Picker-for-SwiftUI
I have tried multiple times to get the filter working with my Firestore data and I am able to get the picker to read the data but then I am unable to filter it.
and the reusable picker struct is
import Combine
import Firebase
import SwiftUI
struct CustomPickerView: View {
#ObservedObject var schoolData = SchoolDataStore()
var datas : SchoolDataStore
var items : [String]
#State private var filteredItems: [String] = []
#State private var filterString: String = ""
#State private var frameHeight: CGFloat = 400
#Binding var pickerField: String
#Binding var presentPicker: Bool
var body: some View {
let filterBinding = Binding<String> (
get: { filterString },
set: {
filterString = $0
if filterString != "" {
filteredItems = items.filter{$0.lowercased().contains(filterString.lowercased())}
} else {
filteredItems = items
}
setHeight()
}
)
return ZStack {
Color.black.opacity(0.4)
VStack {
VStack(alignment: .leading, spacing: 5) {
HStack {
Button(action: {
withAnimation {
presentPicker = false
}
}) {
Text("Cancel")
}
.padding(10)
Spacer()
}
.background(Color(UIColor.darkGray))
.foregroundColor(.white)
Text("Tap an entry to select it")
.font(.caption)
.padding(.leading,10)
TextField("Filter by entering text", text: filterBinding)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
List {
ForEach(schoolData.datas, id: \.id) { i in
Button(action: {
pickerField = i.name
withAnimation {
presentPicker = false
}
}) {
Text(i.name)
}
}
}
}
.background(Color(UIColor.secondarySystemBackground))
.cornerRadius(10)
.frame(maxWidth: 400)
.padding(.horizontal,10)
.frame(height: frameHeight)
.padding(.top, 40)
Spacer()
}
}
.edgesIgnoringSafeArea(.all)
.onAppear {
filteredItems = items
setHeight()
}
}
fileprivate func setHeight() {
withAnimation {
if filteredItems.count > 5 {
frameHeight = 400
} else if filteredItems.count == 0 {
frameHeight = 130
} else {
frameHeight = CGFloat(filteredItems.count * 45 + 130)
}
}
}
}
struct CustomPickerView_Previews: PreviewProvider {
static let sampleData = ["Milk", "Apples", "Sugar", "Eggs", "Oranges", "Potatoes", "Corn", "Bread"].sorted()
static var previews: some View {
CustomPickerView(datas: SchoolDataStore(), items: sampleData, pickerField: .constant(""), presentPicker: .constant(true))
}
}
class SchoolDataStore : ObservableObject{
#Published var datas = [schoolName]()
init() {
let db = Firestore.firestore()
db.collection("School Name").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
for i in snap!.documentChanges{
let id = i.document.documentID
let name = i.document.get("Name") as? String ?? ""
self.datas.append(schoolName(id: id, name: name))
}
}
}
}
struct schoolName : Identifiable, Codable {
var id : String
var name : String
}
I have managed to get the data from Firestore into my picker now, but I am currently unable to filter.
When I change the values of the filteredItems into schoolData.datas I get an error about converting to string or .filter is not a member etc.
Anybody able to point me in the right direction with this please?
Kindest Regards,

Related

How do I update the data in the tabview after the one-to-many coredata has been modified?

Purpose
I want to update the data in the tabview automatically when I return to the RootView after I rename the tag name in the tagManagemenView.
Current Status
When I do delete operation in the TagManagementView, the RootView is able to update automatically.
However, If the Tag name is modified, the RootView display will not be updated, but clicking into the ItemDetailsView is able to display the latest modified name. Only the display of the RootView is not updated.
Background and code
Item and Tag are created using coredata and have a one-to-many relationship, where one Item corresponds to multiple Tags
// RootView
struct RootView: View {
#State private var selection = 0
var body: some View {
NavigationView {
TabView(selection: $selection) {
ItemListView()
.tabItem {
Label ("Items",systemImage: "shippingbox")
}
.tag(0)
Settings()
.tabItem{
Label("Settings", systemImage: "gearshape")
}
.tag(1)
}
.navigationTitle(selection == 0 ? "Items" : "Settings")
}
.navigationViewStyle(.stack)
}
}
// ItemListView
struct ItemListView: View {
#FetchRequest var items: FetchedResults<Item>
#State private var itemDetailViewIsShow: Bool = false
#State private var selectedItem: Item? = nil
init() {
var predicate = NSPredicate(format: "TRUEPREDICATE"))
_items = FetchRequest(fetchRequest: Item.fetchRequest(predicate))
}
var body: some View {
ForEach(items) { item in
Button(action: {
self.selectedItem = item
self.itemDetailViewIsShow = true
}, label: {
ItemCellView(item: item)
})
}
if selectedItem != nil {
NavigationLink (
destination: ItemDetailView(item: selectedItem!, detailViewIsShow: $itemDetailViewIsShow),
isActive: $itemDetailViewIsShow
) {
EmptyView()
}
.isDetailLink(false)
}
}
}
// TagManagementView
struct TagManagementView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: Tag.entity(), sortDescriptors: []) var allTags: FetchedResults<Tag>
#State var isShowDeleteAlert = false
#State var showModel = false
#State var selected: Tag?
var body: some View {
ZStack {
List {
ForEach(allTags) { tag in
TagCellView(tag: tag)
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(role: .destructive, action: {
isShowDeleteAlert = true
selected = tag
}, label: {
Label("Delete", systemImage: "trash")
.foregroundColor(.white)
})
Button(action: {
showModel = true
selected = tag
}, label: {
Label("Edit", systemImage: "square.and.pencil")
.foregroundColor(.white)
})
}
}
.confirmationDialog("Delete confirm", isPresented: self.$isShowDeleteAlert, titleVisibility: .visible) {
Button("Delete", role: .destructive) {
if self.selected != nil {
self.selected!.delete(context: context)
}
}
Button(role: .cancel, action: {
isShowDeleteAlert = false
}, label: {
Text("Cancel")
.font(.system(size: 17, weight: .medium))
})
}
}
if self.showModel {
// background...
Color("mask").edgesIgnoringSafeArea(.all)
TagEditorModal(selected: self.$selected, isShowing: self.$showModel)
}
}
}
}
// TagEditorModal
struct TagEditorModal: View {
#Environment(\.managedObjectContext) var context
#State var tagName: String = ""
#Binding var isShowing: Bool
#Binding var selector: Tag?
init (selected: Binding<Tag?>, isShowing: Binding<Bool>) {
_isShowing = isShowing
_selector = selected
_tagName = .init(wrappedValue: selected.wrappedValue!.name)
}
var body: some View {
VStack{
TextField("Tag name", text: self.$tagName)
HStack {
Button(action: {
self.isShowing = false
}) {
Text("Cancel")
}
Button(action: {
self.selector!.update(name: self.tagName, context: context)
self.isShowing = false
}, label: {
Text("Submit")
})
}
}
}
}
// update tagName func
extension Tag {
func update(name: String, context: NSManagedObjectContext) {
self.name = name
self.updatedAt = Date()
self.objectWillChange.send()
try? context.save()
}
}
// ItemCellView
struct ItemCellView: View {
#Environment(\.managedObjectContext) var context
#ObservedObject var item: Item
var body: some View {
VStack {
Text(item.name)
TagListView(tags: .constant(item.tags))
}
}
}
// tagListView
struct TagListView: View {
#Binding var tags: [Tag]
#State private var totalHeight = CGFloat.zero
var body: some View {
VStack {
GeometryReader { geo in
VStack(alignment: .leading,spacing: 10) {
ForEach (getRows(screenWidth: geo.size.width), id: \.self) {rows in
HStack(spacing: 4) {
ForEach (rows) { tag in
Text(tag.name)
.font(.system(size: 10))
.fontWeight(.medium)
.lineLimit(1)
.cornerRadius(40)
}
}
}
}
.frame(width: geo.size.width, alignment: .leading)
.background(viewHeightReader($totalHeight))
}
}
.frame(height: totalHeight)
}
private func viewHeightReader(_ binding: Binding<CGFloat>) -> some View {
return GeometryReader { geo -> Color in
let rect = geo.frame(in: .local)
DispatchQueue.main.async {
binding.wrappedValue = rect.size.height
}
return .clear
}
}
func getRows(screenWidth: CGFloat) -> [[Tag]] {
var rows: [[Tag]] = []
var currentRow: [Tag] = []
var totalWidth: CGFloat = 0
self.tags.forEach{ tag in
totalWidth += (tag.size + 24)
if totalWidth > (screenWidth) {
totalWidth = (!currentRow.isEmpty || rows.isEmpty ? (tag.size + 24) : 0)
rows.append(currentRow)
currentRow.removeAll()
currentRow.append(tag)
} else {
currentRow.append(tag)
}
}
if !currentRow.isEmpty {
rows.append(currentRow)
currentRow.removeAll()
}
return rows
}
}
I added a TagCellView and then used an #ObservedObject for the tag
struct TagChipsView: View {
#ObservedObject var tag: Tag
let verticalPadding: CGFloat = 2.0
let horizontalPadding: CGFloat = 8.0
var body: some View {
Text(tag.name)
}
}

How to display saved data in TextEditor when view is reopen

I am building the app that will store vertical and horizontal measurements at 12 doors.
In there, for each loop creates 12 Sections with vertical and horizontal readings TextEditors for each door. Each text editor stores values to one of the arrays.
When done entering I used UserDefaults onDissapear() to store both arrays into a dictionary.
Here's the problem: when I put loadArray(), method that supposed to overwrite vertical and horizontal reading arrays, to onAppear() and revisit the view - none of the stored values are showed( even though, it will print the stored data to console onAppear()).
also, when I revisit the view and then start entering the values then sections with stored data are populated...
Bound_test.swift.
import SwiftUI
struct Bound_test: View {
let station : String = "Tuscany"
var body: some View {
NavigationView {
VStack {
Section {
VStack(){
List {
Group {
NavigationLink(destination: Doors_test(direction: "Outbound"), label: {
Text("Out Bound")
})
NavigationLink(destination: Doors_test(direction: "Inbound"), label: {
Text("In Bound")
})
}
.padding(.horizontal,1)
.padding(.vertical,3)
}
.pickerStyle(.wheel)
}
}
}
.navigationTitle("Select direction")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct Bound_test_Previews: PreviewProvider {
static var previews: some View {
Bound_test()
}
}
Doors.swift
import SwiftUI
struct Doors_test: View {
let station : String = "Tuscany"
let direction : String
#State var vM = [String](repeating: "", count: 12) //vert reading
#State var hM = [String](repeating: "", count: 12) //horiz reading
//constructor
init(direction: String) {
self.direction = direction
}
#FocusState private var amountIsFocust: Bool
var body: some View {
Form {
//creates 12 doors automatically
ForEach(0..<12) { door in
Section {
HStack {
Text ("Vert:")
//vert reading
TextEditor(text: $vM[door])
.keyboardType(.decimalPad)
.focused($amountIsFocust)
}
HStack {
Text ("Horizontal:")
//horiz reading
TextEditor(text: $hM[door])
.keyboardType(.decimalPad)
.focused($amountIsFocust)
}
} header: {
Text ("Door # \(door+1)")
}
}
}
.onAppear{
loadArray()
}
.navigationTitle("Enter measurements")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .keyboard) {
Spacer() //moves Done button to the right of the screen
Button("Done") {
amountIsFocust = false
print(amountIsFocust)
}
}
}
.onDisappear {
saveArray() //save array on exit
}
}
//yhis method saves all the readings into dict.
func saveArray() {
UserDefaults.standard.set(vM, forKey: "vReadings \(direction)\(station)")
UserDefaults.standard.set(hM, forKey: "hReadings \(direction)\(station)")
}
//load saved data as an array
func loadArray() {
vM = UserDefaults.standard.object(forKey: "vReadings \(direction)\(station)") as? [String] ?? [String](repeating: "", count: 12)
hM = UserDefaults.standard.object(forKey: "hReadings \(direction)\(station)") as? [String] ?? [String](repeating: "", count: 12)
print(vM)
print(hM)
}
}
struct Doors_test_Previews: PreviewProvider {
static var previews: some View {
Doors_test(direction: "Direction")
}
}
Big thanks for suggesting to use #AppStorage. After some research my code works.
import SwiftUI
struct Doors: View {
// var goingBack : Bool = viewWillDisappear(true)
let fileName = Date.now
let station : String
let direction : String
#AppStorage var vM: [String]
#AppStorage var hM: [String]
init(station: String, direction: String) {
self.station = station
self.direction = direction
//initialize dynamic keys for AppStorage
self._vM = AppStorage(wrappedValue:[String](repeating: "", count: 12) , "Vert \(direction) \(station)")
self._hM = AppStorage(wrappedValue:[String](repeating: "", count: 12), "Horiz \(direction) \(station)")
}
#FocusState private var amountIsFocust: Bool
var body: some View {
Form {
ForEach(0..<12) { door in
Section {
HStack {
Text ("Vert:")
TextEditor(text: $vM[door])
.keyboardType(.decimalPad)
.focused($amountIsFocust)
}
HStack {
Text ("Horizontal:")
TextEditor(text: $hM[door])
.keyboardType(.decimalPad)
.focused($amountIsFocust)
}
} header: {
Text ("Door # \(door+1)")
}
}
}
.onAppear{
loadArray()
}
.navigationTitle("Enter measurements")
.navigationBarTitleDisplayMode(.inline)
.toolbar{
ToolbarItemGroup(placement: .keyboard) {
Spacer() //moves Done button to the right of the screen
Button("Done") {
amountIsFocust = false
print(amountIsFocust)
}
}
}
.onDisappear {
saveArray()
}
}
func saveArray() {
UserDefaults.standard.set(vM, forKey: "Vert \(direction) \(station)")
UserDefaults.standard.set(hM, forKey: "Horiz \(direction) \(station)")
}
func loadArray() {
vM = UserDefaults.standard.object(forKey: "Vert \(direction) \(station)") as? [String] ?? [String](repeating: "", count: 12)
hM = UserDefaults.standard.object(forKey: "Horiz \(direction) \(station)") as? [String] ?? [String](repeating: "", count: 12)
print(vM)
print(hM)
}
}
struct Doors_Previews: PreviewProvider {
static var previews: some View {
Doors(station: "Station", direction: "Direction")
}
}

Why do I lose Data here?(SwiftUI)

I have two pages in my app TodayPage and CalendarList page.
I use EnvironmentObject wrapper to pass data between these two pages.
When TodayPage appears on onAppear modifier I call a function to generate days of calendar for me till now everything works fine when I add text to the list of TodayPage then go to the calendarList page and come back again to TodayPage all of the text that I addd to list are gone.I find out I can avoid lost of data by adding simple if to onAppear but I'm not sure this solution is right.
I have to upload lots of code ,Thanks for your help
( DataModel ) :
import SwiftUI
import Foundation
import Combine
struct Day : Identifiable {
var id = UUID()
var name : String
var date : String
var month : String
var List : [Text1?]
}
struct Text1 : Identifiable , Hashable{
var id = UUID()
var name: String
var color: Color
}
class AppState : ObservableObject {
#Published var dataLoaded = false
#Published var allDays : [Day] = [.init(name : "",date: "",month: "",List : [])]
func getDays(number: Int) -> [Day] {
let today = Date()
let formatter = DateFormatter()
return (0..<number).map { index -> Day in
let date = Calendar.current.date(byAdding: .day, value: index, to: today) ?? Date()
return Day(name: date.dayOfWeek(withFormatter: formatter) ?? "", date: "\(Calendar.current.component(.day, from: date))", month: date.nameOfMonth(withFormatter: formatter) ?? "", List: [])
}
}
}
extension Date {
func dayOfWeek(withFormatter dateFormatter: DateFormatter) -> String? {
dateFormatter.dateFormat = "EEEE"
return dateFormatter.string(from: self).capitalized
}
func nameOfMonth(withFormatter dateFormatter: DateFormatter) -> String? {
dateFormatter.dateFormat = "LLLL"
return dateFormatter.string(from: self).capitalized
}
}
class AddListViewViewModel : ObservableObject {
#Published var textItemsToAdd : [Text1] = [.init(name: "", color: .clear)] //start with one empty item
func saveToAppState(appState: AppState) {
appState.allDays[0].List.append(contentsOf: textItemsToAdd.filter {
!$0.name.isEmpty })
}
func bindingForId(id: UUID) -> Binding<String> {
.init { () -> String in
self.textItemsToAdd.first(where: { $0.id == id })?.name ?? ""
} set: { (newValue) in
self.textItemsToAdd = self.textItemsToAdd.map {
guard $0.id == id else {
return $0
}
return .init(id: id, name: newValue, color: .clear)
}
}
}
}
List view :
struct ListView: View {
#State private var showAddListView = false
#EnvironmentObject var appState : AppState
#Binding var dayList : [Text1?]
var title : String
var body: some View {
NavigationView {
VStack {
ZStack {
List(dayList, id : \.self){ text in
Text(text?.name ?? "")
}
if showAddListView {
AddListView(showAddListView: $showAddListView)
.offset(y:-100)
}
}
}
.navigationTitle(title)
.navigationBarItems(trailing:
Button(action: {showAddListView = true}) {
Image(systemName: "plus")
.font(.title2)
}
)
}
}
}
pop up menu View(for adding text into the list)
struct AddListView: View {
#Binding var showAddListView : Bool
#EnvironmentObject var appState : AppState
#StateObject private var viewModel = AddListViewViewModel()
var body: some View {
ZStack {
Title(addItem: { viewModel.textItemsToAdd.append(.init(name: "", color: .clear)) })
VStack {
ScrollView {
ForEach(viewModel.textItemsToAdd, id: \.id) { item in //note this is id: \.id and not \.self
PreAddTextField(textInTextField: viewModel.bindingForId(id: item.id))
}
}
}
.padding()
.offset(y: 40)
Buttons(showAddListView: $showAddListView, save: {
viewModel.saveToAppState(appState: appState)
})
}
.frame(width: 300, height: 200)
.background(Color.white)
.shadow(color: Color.black.opacity(0.3), radius: 10, x: 0, y: 10)
}
}
struct PreAddTextField: View {
#Binding var textInTextField : String
var body: some View {
VStack {
TextField("Enter text", text: $textInTextField)
}
}
}
struct Buttons: View {
#Binding var showAddListView : Bool
var save : () -> Void
var body: some View {
VStack {
HStack(spacing:100) {
Button(action: {
showAddListView = false}) {
Text("Cancel")
}
Button(action: {
showAddListView = false
save()
}) {
Text("Add")
}
}
}
.offset(y: 70)
}
}
struct Title: View {
var addItem : () -> Void
var body: some View {
VStack {
HStack {
Text("Add Text to list")
.font(.title2)
Spacer()
Button(action: {
addItem()
}) {
Image(systemName: "plus")
.font(.title2)
}
}
.padding()
Spacer()
}
}
}
TodayPage View :
struct TodayPage: View {
#EnvironmentObject var appState : AppState
var body: some View {
ListView(dayList: $appState.allDays[0].List, title: "Today")
.onAppear {
// To avoid data lost , we can use simple if below but I'm not sure it's a right solution
// if appState.dataLoaded == false {
appState.allDays = appState.getDays(number: 365)
// appState.dataLoaded = true
// }
}
}
}
CalendarListPage :
struct CalendarList: View {
#EnvironmentObject var appState : AppState
var body: some View {
NavigationView {
List {
ForEach(appState.allDays.indices, id:\.self) { index in
NavigationLink(destination: ListView(appState: _appState, dayList: $appState.allDays[index].List, title: appState.allDays[index].name).navigationBarTitleDisplayMode(.inline) ) {
HStack(alignment: .top) {
RoundedRectangle(cornerRadius: 23)
.frame(width: 74, height: 74)
.foregroundColor(Color.blue)
.overlay(
VStack {
Text(appState.allDays[index].date)
.font(.system(size: 35, weight: .regular))
.foregroundColor(.white)
Text(appState.allDays[index].month)
.foregroundColor(.white)
}
)
.padding(.trailing ,4)
VStack(alignment: .leading, spacing: 5) {
Text(appState.allDays[index].name)
.font(.system(size: 20, weight: .semibold))
}
}
.padding(.vertical ,6)
}
}
}
.navigationTitle("Calendar")
}.onAppear {
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
and finally TabBar :
struct TabBar: View {
var body: some View {
let appState = AppState()
TabView {
TodayPage().tabItem {
Image(systemName: "info.circle")
Text("Today")
}
CalendarList().tabItem {
Image(systemName: "square.fill.text.grid.1x2")
Text("Calendar")
}
}
.environmentObject(appState)
}
}
Right now, because your let appState is inside the body of TabBar, it gets recreated every time TabBar is rendered. Instead, store it as a #StateObject (or #ObservedObject if you are pre iOS 14):
struct TabBar: View {
#StateObject var appState = AppState()
var body: some View {
TabView {
TodayPage().tabItem {
Image(systemName: "info.circle")
Text("Today")
}
CalendarList().tabItem {
Image(systemName: "square.fill.text.grid.1x2")
Text("Calendar")
}
}
.onAppear {
appState.allDays = appState.getDays(number: 365)
}
.environmentObject(appState)
}
}
Then, remove your other onAppear on TodayPage

SwiftUI nested LazyVStacks in a single ScrollView

I'm trying to build a comment thread. So top level comments can all have nested comments and so can they and so on and so forth. But I'm having issues around scrolling and also sometimes when expanding sections the whole view just jumps around, and can have a giant blank space at the bottom. The code looks like this:
struct ContentView: View {
var body: some View {
VStack {
HStack {
Text("Comments")
.font(.system(size: 34))
.fontWeight(.bold)
Spacer()
}
.padding()
CommentListView(commentIds: [0, 1, 2, 3], nestingLevel: 1)
}
}
}
struct CommentListView: View {
let commentIds: [Int]?
let nestingLevel: Int
var body: some View {
if let commentIds = commentIds {
LazyVStack(alignment: .leading) {
ForEach(commentIds, id: \.self) { id in
CommentItemView(viewModel: CommentItemViewModel(commentId: id), nestingLevel: nestingLevel)
}
}
.applyIf(nestingLevel == 1) {
$0.scrollable()
}
} else {
Spacer()
Text("No comments")
Spacer()
}
}
}
struct CommentItemView: View {
#StateObject var viewModel: CommentItemViewModel
let nestingLevel: Int
#State private var showComments = false
var body: some View {
VStack {
switch viewModel.viewState {
case .error:
Text("Error")
.fontWeight(.thin)
.font(.system(size: 12))
.italic()
case .loading:
Text("Loading")
.fontWeight(.thin)
.font(.system(size: 12))
.italic()
case .complete:
VStack {
Text(viewModel.text)
.padding(.bottom)
.padding(.leading, 20 * CGFloat(nestingLevel))
if let commentIds = viewModel.commentIds {
Button {
withAnimation {
showComments.toggle()
}
} label: {
Text(showComments ? "Hide comments" : "Show comments")
}
if showComments {
CommentListView(commentIds: commentIds, nestingLevel: nestingLevel + 1)
}
}
}
}
}
}
}
class CommentItemViewModel: ObservableObject {
#Published private(set) var text = ""
#Published private(set) var commentIds: [Int]? = [0, 1, 2, 3]
#Published private(set) var viewState: ViewState = .loading
private let commentId: Int
private var viewStateInternal: ViewState = .loading {
willSet {
withAnimation {
viewState = newValue
}
}
}
init(commentId: Int) {
self.commentId = commentId
fetchComment()
}
private func fetchComment() {
viewStateInternal = .complete
text = CommentValue.allCases[commentId].rawValue
}
}
Has anyone got a better way of doing this? I know List can now accept a KeyPath to child object and it can nest that way, but there's so limited design control over List that I didn't want to use it. Also, while this code is an example, the real code will have to load each comment from an API call, so List won't perform as well as LazyVStack in that regard.
Any help appreciated - including a complete overhaul of how to implement this sort of async loading nested view.

CircleImage keeps changing colour after selecting an option in picker and adding to list

I'm having this weird issue where the colour for an item in a list changes when a new item with a different colour is added, essentially it doesn't retain its colour-value but takes up a new one.
What I'm trying to do is to show a colour that corresponds to the priority level the user has selected.
Here is the code:
struct PriorityGreen: View {
var body: some View {
Circle()
.frame(width: 20, height: 20)
.foregroundColor(Color.green)
}
}
struct PriorityYellow: View {
var body: some View {
Circle()
.frame(width: 20, height: 20)
.foregroundColor(Color.yellow)
}
}
struct PriorityOrange: View {
var body: some View {
Circle()
.frame(width: 20, height: 20)
.foregroundColor(Color.orange)
}
}
struct PriorityRed: View {
var body: some View {
Circle()
.frame(width: 20, height: 20)
.foregroundColor(Color.red)
}
}
Code for view
import SwiftUI
struct AppView: View {
#ObservedObject var data = Model()
#State var showViewTwo = false
var body: some View {
NavigationView {
VStack {
List {
ForEach(data.arrayOfTask, id: \.self) { row in
HStack {
if self.data.priority == 0 {
PriorityGreen()
} else if self.data.priority == 1 {
PriorityYellow()
} else if self.data.priority == 2 {
PriorityOrange()
} else if self.data.priority == 3 {
PriorityRed()
}
Text("\(row)")
}
}
.onDelete(perform: removeItems).animation(.default)
}
.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
}
.navigationBarTitle("Tasks")
.navigationBarItems(leading:
EditButton().animation(.default),
trailing: Button(action: {
self.showViewTwo.toggle()
}) {
Text("New task")
}.sheet(isPresented: $showViewTwo) {
ViewTwo(data: self.data, showViewTwo: self.$showViewTwo)
})
}
}
func removeItems(at offset: IndexSet) {
data.arrayOfTask.remove(atOffsets: offset)
}
}
struct AppView_Previews: PreviewProvider {
static var previews: some View {
AppView()
}
}
struct ViewTwo: View {
#State var data: Model
#State var newName = ""
#State var newCatergory = ""
#State var newPriorityLevel = ""
#State var defaultPriorityLevel = 1
#State var priorityTypes = ["low", "medium", "high", "critical"]
#Binding var showViewTwo: Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Add task name")) {
TextField("Name", text: $newName)
/*
This section will be implementated later on
TextField("Catergory", text: $newCatergory)
*/
}
Section(header: Text("Select task priority")) {
Picker("Priority Levels", selection: $defaultPriorityLevel) {
ForEach(0..<priorityTypes.count) {
Text(self.priorityTypes[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
.navigationBarTitle("New task details")
.navigationBarItems(trailing:
Button("Save") {
self.showViewTwo.toggle()
self.data.taskName = self.newName
self.data.arrayOfTask.append(self.newName)
self.data.priority = self.defaultPriorityLevel
})
}
}
}
struct PriorityCirleView: View {
var body: some View {
Circle()
.frame(width: 20, height: 20)
.foregroundColor(Color.green)
}
}
import SwiftUI
enum Catergory {
case work
case home
case family
case health
case bills
}
enum Priority {
case low
case medium
case high
case critical
}
class Model: ObservableObject {
#Published var taskName = ""
#Published var taskCategory = ""
#Published var priority = 0
#Published var arrayOfTask = [String]()
}
This gif demonstrates the problem more clearly
(Gif)[https://imgur.com/a/ffzpSft]
You only have one priority in your model instead of a priority per task.
Change your model to this:
class Model: ObservableObject {
struct Task {
var taskName = ""
var taskCategory = ""
var priority = 0
}
#Published var arrayOfTask = [Task]()
}
And update your code to use the new model:
struct AppView: View {
#ObservedObject var data = Model()
#State var showViewTwo = false
var body: some View {
NavigationView {
VStack {
List {
ForEach(data.arrayOfTask, id: \.taskName) { task in
HStack {
if task.priority == 0 {
PriorityGreen()
} else if task.priority == 1 {
PriorityYellow()
} else if task.priority == 2 {
PriorityOrange()
} else if task.priority == 3 {
PriorityRed()
}
Text("\(task.taskName)")
}
}
.onDelete(perform: removeItems).animation(.default)
}
.listStyle(GroupedListStyle())
.environment(\.horizontalSizeClass, .regular)
}
.navigationBarTitle("Tasks")
.navigationBarItems(leading:
EditButton().animation(.default),
trailing: Button(action: {
self.showViewTwo.toggle()
}) {
Text("New task")
}.sheet(isPresented: $showViewTwo) {
ViewTwo(data: self.data, showViewTwo: self.$showViewTwo)
})
}
}
func removeItems(at offset: IndexSet) {
data.arrayOfTask.remove(atOffsets: offset)
}
}
struct ViewTwo: View {
#State var data: Model
#State var newName = ""
#State var newCatergory = ""
#State var newPriorityLevel = ""
#State var defaultPriorityLevel = 1
#State var priorityTypes = ["low", "medium", "high", "critical"]
#Binding var showViewTwo: Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Add task name")) {
TextField("Name", text: $newName)
/*
This section will be implementated later on
TextField("Catergory", text: $newCatergory)
*/
}
Section(header: Text("Select task priority")) {
Picker("Priority Levels", selection: $defaultPriorityLevel) {
ForEach(0..<priorityTypes.count) {
Text(self.priorityTypes[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}
}
.navigationBarTitle("New task details")
.navigationBarItems(trailing:
Button("Save") {
var task = Model.Task()
self.showViewTwo.toggle()
task.taskName = self.newName
task.priority = self.defaultPriorityLevel
self.data.arrayOfTask.append(task)
})
}
}
}
Using the taskName as the id is not a good idea. Update your Task struct to include a unique value:
class Model: ObservableObject {
struct Task: Identifiable {
static var uniqueID = 0
var taskName = ""
var taskCategory = ""
var priority = 0
var id = 0
init() {
Task.uniqueID += 1
self.id = Task.uniqueID
}
}
#Published var arrayOfTask = [Task]()
}
And then change:
ForEach(data.arrayOfTask, id: \.taskName) { task in
to
ForEach(data.arrayOfTask) { task in