SwuiftUI check mark single row - arrays

on my project in SwiftUi I'm listing some data in a form, I want to give the user the ability to select a single item in the list with the tap action( can not select more than one in the list)
on the code below I have create a list:
List {
ForEach(listOfCycle, id: \.self) {db in
dbList(db: db, ciclo: self.$cycleSelected)
}
}
and for each row I have the view dbList
import SwiftUI
struct dbList: View {
#State var db : Cycle
#Binding var ciclo : Cycle?
#State var cicloSelected : Bool = false
var body: some View {
HStack{
Text("Database:")
Spacer()
Text(db.idDatabaseAirports ?? "").foregroundColor(self.cicloSelected ? .green: .black).font(self.cicloSelected ? .title : .body)
if self.cicloSelected {
Image(systemName: "checkmark.circle")
}
}.onTapGesture {
self.cicloSelected.toggle()
self.ciclo = self.db
}
}
}
the logic work but the user can tap on multiple row and select more than one, on my project I have to put the checkmark only on one row at the time can't be more than one.
is there any way I can put to avoid multiple selection.
Thanks a lot

The provided code is not testable, so only idea (scratchy, but should be clear).
Note: make sure that Cycle is Equatable
struct dbList: View {
#State var db : Cycle
#Binding var ciclo : Cycle?
var body: some View {
HStack{
Text("Database:")
Spacer()
Text(db.idDatabaseAirports ?? "").foregroundColor(self.db == self.cyclo ? .green: .black).font(self.cicloSelected ? .title : .body)
if self.db == self.cyclo {
Image(systemName: "checkmark.circle")
}
}.onTapGesture {
self.ciclo = self.db
}
}
}

Related

AppStorage - Array - Error: No exact matches in call to initializer

I am developing an App for increasing productivity. My Main Goal in this file is to add Views over a dialog. Another target is to save the data in an Array for using it again with the annotation #AppStorage.
struct Task : Identifiable {
var id = UUID()
var myContent = "Empty"
var myCounter = 0
}
I'm using this struct to save my data which is here mainly the tasks name.
struct TaskView : View {
var task : Task
var body: some View {
HStack {
Spacer()
Text(String(task.myContent) ?? "test")
Spacer()
Text("Sessions today: " + String(task.myCounter))
Spacer()
Image(systemName: "xmark.bin.fill")
}
}
}
For displaying the data I'm using my own struct.
struct ItemList: View {
#AppStorage("myviews") var myviews : [Task]? = nil
#State private var showingAlert = false;
#State private var taskName = "tim";
var body: some View {
VStack{
if(!myviews.isEmpty){
for task in myviews {
TaskView(task: task)
}
}
Spacer()
Button {
showingAlert = true;
} label: {
Image(systemName: "plus")
.padding()
.background(Color.red)
.accentColor(.white)
.cornerRadius(100)
}
.alert(isPresented: $showingAlert) {
Alert(title: Text("Important message"), message: TextField("Task: "; text: $taskName), primaryButton: .destructive(Text("Got it!")){
myviews.append(Task(myContent: String(taskName), myCounter: 0))
})
}
Spacer()
}
}
}
So the main part consists of my #AppStorage Array, a loop to show existing "tasks" and a Dialog to add these tasks to the array.
The Error I am getting is the "No exact matches in call to initializer" directly in the line #AppStorage("myviews") var myviews : [Task]? = nil
I already tried different variations of initializing the array until I read in a forum that leaving the initialization not optional could be a cause to my problems.
Furthermore I checked my "Text" - Fields for the wrong types and casted the Int's (myCounter) to String.
It feels like I read every StackOverflow Article regarding my Error but none could help me.

How to bind a list of textfields that edit a variable within an a core data array?

Swift ui requires a Binding<String> to link to the value you are updating in a text field. Much like the native iPhone Reminders app, I am looking to permit inline editing a list that will persist.
The attached code works only but gives the same name for each item due to them all being bound to the same variable. How can I bind this to the [FruitEntity] array?
class CoreDataViewModel: ObservableObject {
//static let instance = CoreDataViewModel()
let container: NSPersistentContainer
let context: NSManagedObjectContext
#Published var savedEntities: [FruitEntity] = []
}
struct Screen: View {
#StateObject var vm = CoreDataViewModel()
var body: some View {
List{
ForEach(vm.savedEntities, id: \.self) {entity in
VStack{
HStack {
TextField("\(entity.name ?? "Workout Name...")", text: $questionVariable)
.onChange(of: entity.name) { text in
entity.name = questionVariable
}
}
.onDelete(perform: vm.deleteFruit)
.onMove(perform: moveItem)
}
}
}
}
}
You can just move the TextField to a separate view, with its own #State var for the field and another var for the entity.
Create a view like the following one:
struct ChangeName: View {
// Will change the entity
let entity: FruitEntity
// Will update the field
#State private var questionVariable = ""
var body: some View {
TextField("\(entity.name ?? "Workout Name...")", text: $questionVariable)
.onChange(of: questionVariable) { text in
entity.name = text
// Remember to save the persistent container/ managed-object-context
}
}
}
Call it in your main view:
struct Screen: View {
List{
ForEach(vm.savedEntities, id: \.self) {entity in
VStack{
HStack {
ChangeName(entity: entity)
}
}
.onDelete(perform: vm.deleteFruit)
.onMove(perform: moveItem)
}
}
}

SwiftUI: String property of an object is not displaying in Text, and how would you edit a string in an object?

I am very new to programming in Swift. So I'm trying to come up with a time management program. I have posted some code that have been derived from my project that is a work in progress, and I'm trying to troubleshoot some issues that I'm having that come from my lack of knowledge regarding Swift and SwiftUI. I would like to ask two questions here, but if you only have the answer to just one of them, I would greatly appreciate it.
So in my ContentView, I'm trying to display the taskName of the object with ID 0 using a Text in a VStack -- however, it is not displaying, and I'm not sure of the reason why. I can display the taskLength by putting it inside the String method, but taskName is not coming up when I attempt to display it.
Also I'm attempting to change the taskName of Task(id: 0) that is being passed into display2 directly from the display2, but I'm not sure if the taskName of Task(id: 0) is actually being changed, or it's only the taskName of #State var task:Task in display2 that is being changed -- based on my intuitions, I would think the latter case is actually happening. In that case, is there a way to directly edit the taskName of Task(id: 0) from display2?
import SwiftUI
import Foundation
import Combine
struct Task: Hashable, Codable, Identifiable {
var id: Int
var taskName: String = ""
var taskLength: Int = 0
var isBreak : Bool = false
}
class ModelData : ObservableObject{
#Published var tasks: [Task] = [
Task(id: 0,taskName: "Test", taskLength: 34, isBreak: false),
Task(id: 1,taskName: "Math", taskLength: 30, isBreak: false),
Task(id: 2,taskName: "Science", taskLength: 40, isBreak: false)
]
}
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
VStack{
Text(Task(id: 0).taskName)
display2(task:Task(id: 0))
}
}
}
struct display2: View{
#State var task:Task
var body: some View {
TextField("New task",text: $task.taskName)
}
}
The problem is here:
Text(Task(id: 0).taskName)
Here, you're creating a new Task, with an id of 0. This is not the first task inside your ModelData's tasks array.
Instead, reference the first task via subscript []:
Text(modelData.tasks[ /* index of task */ ].taskName)
Normally you can just put 0 here to get the first Task. However, you said you actually want the Task with an id of 0. You can do this via firstIndex(where:).
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
VStack{
Text(
modelData.tasks[getTaskIndexFrom(id: 0)] /// access
.taskName
)
Display2( /// see https://stackoverflow.com/a/67064699/14351818
task: $modelData.tasks[getTaskIndexFrom(id: 0)]
)
}
}
func getTaskIndexFrom(id: Int) -> Int {
/// get first index of a task with the specified `id`
if let firstIndex = modelData.tasks.firstIndex(where: { $0.id == 0 }) {
return firstIndex
} else {
return 0
}
}
}
struct Display2: View{
#Binding var task: Task /// need a Binding here
var body: some View {
TextField("New task", text: $task.taskName)
}
}
Ok, your second question:
In that case, is there a way to directly edit the taskName of Task(id: 0) from display2?
Yep! Just use #Binding on Display2's task. This way, all changes will be synced back to your modelData.
In ContentView you used just Task(), but you have to use modelData for #Published var tasks in ModelData.
Task(id: 0).taskName -> modelData.tasks[1].taskName
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
var body: some View {
VStack{
Text(modelData.tasks[1].taskName)
.foregroundColor(.blue)
display2(task:Task(id: 0))
}
}
}
Also, as long as you use #EnvironmentObject, you need to add .environmentObject to the main as well.
(The code below is an example of the SwiftUI life cycle)
import SwiftUI
#main
struct ReplyToStackoverflowApp: App {
var modelData: ModelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData)
}
}
}

How to update variables in ContentView with SheetView function call?

I have a code with a sheet view and a normal view. When I press a button within my sheet view I make an API call. This API call then updates some variables which I'm trying to display in my regular view using ´ForEach´. However, when I make the call in the sheet view and close it down, the array does not seem to update in my normal view. My view just remains blank (except for displaying the button that says "Show sheet". How do I make the array update so that it isn't blank?
Here is my regular view:
// MARK: - Schedule View
struct ScheduleView: View {
#State var selectedTab = 0
#State private var showingSheet = true
var body: some View {
GeometryReader { geo in
VStack {
ForEach(SheetView().vm.Trips, id: \.self) { dict in
Text(dict["Origin"]!) // I want this varible to update, but I doesn't
Text(dict["Destination"]!) // It instead remains blank
}
Button("Show sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView()
}
.frame(width: geo.size.width*0.7, height: geo.size.height*0.06)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(11)
.position(x: geo.size.width/2, y: geo.size.height/2)
// MARK: - Padding funkar inte
}
}.padding()
}
}
And here is my sheet view:
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
#StateObject var vm: PlanTripViewModel = PlanTripViewModel()
#State var selected = 0
var body: some View {
GeometryReader {geo in
ZStack{
VStack {
TextField("From", text: $vm.origin.input).padding()
TextField("To", text: $vm.dest.input).padding()
TextField("00:00", text: $vm.arrivalTime).padding()
TextField("yyyy-mm-dd", text: $vm.travelDate).padding()
Button("Add trip") {
vm.fetchStatus = .start // This starts the API call in another file of mine
presentationMode.wrappedValue.dismiss() // This closes the sheet view
}.padding()
}.foregroundColor(.blue)
}
}
}
}
Right now, you're making a new SheetView instance on every single ForEach call -- it's not the same one that you're using in your sheet call.
To solve this, you'll want to store the state in your parent view and give the sheet view a reference to it.
struct SheetView: View {
#Environment(\.presentationMode) var presentationMode
#ObservedObject var vm: PlanTripViewModel //<-- Here
#State var selected = 0
var body: some View {
GeometryReader {geo in
ZStack{
VStack {
TextField("From", text: $vm.origin.input).padding()
TextField("To", text: $vm.dest.input).padding()
TextField("00:00", text: $vm.arrivalTime).padding()
TextField("yyyy-mm-dd", text: $vm.travelDate).padding()
Button("Add trip") {
vm.fetchStatus = .start // This starts the API call in another file of mine
presentationMode.wrappedValue.dismiss() // This closes the sheet view
}.padding()
}.foregroundColor(.blue)
}
}
}
}
struct ScheduleView: View {
#State var selectedTab = 0
#State private var showingSheet = true
#StateObject var vm: PlanTripViewModel //<-- Here
var body: some View {
GeometryReader { geo in
VStack {
ForEach(vm.Trips, id: \.self) { dict in
Text(dict["Origin"]!)
Text(dict["Destination"]!)
}
Button("Show sheet") {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
SheetView(vm: vm) //<-- Here
}
.frame(width: geo.size.width*0.7, height: geo.size.height*0.06)
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(11)
.position(x: geo.size.width/2, y: geo.size.height/2)
// MARK: - Padding funkar inte
}
}.padding()
}
}
(Note: you may know this already, but force unwrapping your dictionary values with ! will cause a crash if the keys don't exist. You may want to use optional binding (if let) or another safe check to make sure they exist.)

SwiftUI - How do I create a Picker that selects values from a property?

I have these two models, the first one:
import SwiftUI
import Combine
class FolderModel : Codable, Identifiable, Equatable, ObservableObject {
var id = UUID()
var folderName : String
var values : [ValueModel] = []
init(folderName: String) {
self.folderName = folderName
}
}
And the second one:
import SwiftUI
import Combine
class ValueModel : Codable, Identifiable, Equatable, ObservableObject, Comparable {
var id = UUID()
var name : String
var notes : String?
var expires : Date?
init(name: String, notes: String?, expires: Date?) {
self.name = name
self.notes = notes
self.expires = expires
}
}
And these storages:
import SwiftUI
import Combine
class DataManager : Equatable, Identifiable, ObservableObject {
static let shared = DataManager()
#Published var storageValues : [ValueModel] = []
typealias StorageValues = [ValueModel]
#Published var storageFolder : [FolderModel] = []
typealias StorageFolder = [FolderModel]
//The rest of the code
}
And then I have a Detail View of the Value, which shows all of his properties. From there, I would like to select the folder that the user wants to put it in (which in code translates to appending that value into the array "values" of the FolderModel).
To do this, I tried to create a Picker that display all the folders (by name) and that can be selected, so that when I press "Save", I can do something like "selectedFolder.append(value)". The Picker I tried to create is this:
import SwiftUI
struct DetailValueView: View {
#ObservedObject var dm : DataManager
#State private var selector = 0
#State var selectedFolder : FolderModel?
var body: some View {
Form {
Section(header: Text("Properties")) {
folderCell
if hasFolder == true {
picker
}
}
}
}
var folderCell : some View {
VStack {
Toggle(isOn: $hasFolder) {
if hasFolder == true {
VStack(alignment: .leading) {
Text("Folder: " + "//Here I would like to display the selected value")
}
} else if hasFolder == false {
Text("Folder")
}
}
}
}
var picker : some View {
Picker(selection: $selector, label: Text("Select Folder")) {
ForEach(dm.storageFolder) { foldersForEach in
Button(action: {
selectedFolder = foldersForEach
}, label: {
Text(foldersForEach.folderName)
})
}
}.pickerStyle(DefaultPickerStyle())
}
I tried to find a solution online but I don't really understand how the Picker works, I don't understand how to use that "#State private var selector = 0" to get the value that I want.
Thanks to everyone who will help me!
Two things to stress here: First, you need to either wrap your form in a NavigationView or change the picker style to WheelPickerStyle. Otherwise the picker won't work (see here for a detailed explanation). Second, your state selector is of type integer. So make sure to loop through integers as well. Now your state selector holds the index of the selected folder from the list of folders.
Please see my working example below:
struct ContentView: View {
#ObservedObject var dm: DataManager
#State private var selector = 0
#State private var hasFolder = false
var body: some View {
NavigationView {
Form {
Section(header: Text("Properties")) {
folderCell
if !dm.storageFolder.isEmpty {
picker
}
}
}
}
}
var folderCell : some View {
VStack {
Toggle(isOn: $hasFolder) {
if hasFolder == true {
VStack(alignment: .leading) {
Text("Folder: \(dm.storageFolder[selector].folderName)")
}
} else if hasFolder == false {
Text("Folder")
}
}
}
}
var picker : some View {
Picker(selection: $selector, label: Text("Select Folder")) {
ForEach(0 ..< dm.storageFolder.count) {
Text(dm.storageFolder[$0].folderName)
}
}.pickerStyle(DefaultPickerStyle())
}
}

Resources