How to use On appear() with SwiftUI on iOS 16 - swiftui

I didn't change anything in my code, but since iOS16 the onAppear() part is not working.
selectedParcoursIndexArray.append(0) does not execute,
And app crash on :
get: { self.selectedParcoursIndexArray[num] },
Because selectedParcoursIndexArray is empty. And should be filled while VStack appear.
My code :
import SwiftUI
struct DepartsSimultanesView: View {
#EnvironmentObject var objCourse : CourseActuelle
#EnvironmentObject var zindex : Zindex
#EnvironmentObject var listActuelle : ListActuelle
#State var selectedParcoursIndexArray : [Int] = [0]
#State var peutSauvegarder = false
#State var groupeManager : GroupeManager
var listeParcoursRestants : [String] = []
#State private var action: Int? = 0
var body: some View {
VStack{
HStack{
...
}
HStack{
...
}
Form{
Section(header: Text("Coureurs/Choix parcours")){
ForEach(0 ..< groupeManager.groupeList.count, id:\.self){ num in
let detailManager = DetailManager(groupeId: groupeManager.groupeList[num].id, courseId: objCourse.id!)
let listeParcoursRestants = listRestants(detailManager: detailManager)
let enCourse = detailManager.enCourseList.filter { $0.groupeId == groupeManager.groupeList[num].id }
Picker(selection: Binding(
get: { self.selectedParcoursIndexArray[num] },
set: { (newValue) in
self.selectedParcoursIndexArray[num] = newValue
peutSauvegarder = true
}), label: HStack{
Image(systemName: "person.fill")
Text("\(groupeManager.groupeList[num].nomGroupe)")
}.foregroundColor(selectedParcoursIndexArray[num] != 0 ? .orange : Color("ColorDarkLight"))
) {
if enCourse.count != 0 {
...
}
}.navigationBarHidden(true)
.disabled(listeParcoursRestants.count == 1 || enCourse.count != 0)
}
}
}
}
.onAppear(){
groupeManager = GroupeManager(courseId: objCourse.id!)
for _ in (0 ..< groupeManager.groupeList.count) {
selectedParcoursIndexArray.append(0)
}
}
.navigationBarHidden(true)
NavigationLink(destination: CestPartiView().environmentObject(objCourse), tag: 1, selection: $action){}
}
}

ForEach is not a for loop, it'll crash if you try to access an array by index inside its closure. id:\.self is a mistake, the id needs to be a property of the data it cannot be the data itself.
onAppear is designed to call an external action not to initialise the data that will be displayed. Move that initialisation somewhere else like to an #State struct's init.

Related

SwiftUI- passing #State variables to multiple views trouble

Im trying to make an app similar to iphones reminders app in terms of UI. I have a view where I have a list that I can add and delete items from, I also have a view for adding an item that allows me to name the item and select some options, and I have another view for when an item in the list is selected and I want to be able to show the name and options I made but it doesn't display. The code for the list view
struct DotView: View {
enum ActiveSheet: String, Identifiable {
case SwiftUIView, EditView
var id: String {
return self.rawValue
}
}
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var toggleNotification:Bool
#State var activeSheet : ActiveSheet? = nil
#Binding var show : Bool
var body: some View {
ZStack{
HStack {
EditButton()
.padding(.leading)
Spacer()
Button(action: {
self.activeSheet = .SwiftUIView
}, label: {
Text("Add")
})
.padding(.trailing)
}
ZStack {
List {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
Button(action: { self.activeSheet = .EditView }, label: {
DotItemListView(dotitem: dotitem)
})
}
.onDelete(perform: listViewModel.deleteItem)
.onMove(perform: listViewModel.moveItem)
.listRowBackground(Color("textBG"))
}.listStyle(PlainListStyle())
.background(Color("textBG"))
.frame(height: 300)
.cornerRadius(10)
.padding(.horizontal)
}
}
.sheet(item: $activeSheet){ sheet in
switch sheet {
case .SwiftUIView:
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show)
case .EditView:
EditView(show:$show)
}
}
When I add an item to the list it shows this in each row
struct DotItemListView:View {
let dotitem: DotItem
var body: some View{
HStack {
Text(dotitem.dotName)
Spacer()
Text(dotitem.selection)
Spacer()
Text(dotitem.dotNumber)
Spacer()
}
}
}
This is how I'm adding each item to the list
struct DotItem:Equatable, Codable{
var dotId = UUID().uuidString
let dotName:String
let dotNumber:String
let selection:String
}
class ListViewModel: ObservableObject {
#Published var dotitems: [DotItem] = [] {
didSet {
saveItem()
}
}
let dotitemsKey: String = "dotitems_list"
init() {
getDotItems()
}
func getDotItems() {
guard
let data = UserDefaults.standard.data(forKey: dotitemsKey),
let savedDotItems = try? JSONDecoder().decode([DotItem].self, from: data)
else { return }
self.dotitems = savedDotItems
}
func deleteItem(indexSet: IndexSet){
dotitems.remove(atOffsets: indexSet)
}
func moveItem(from: IndexSet, to: Int){
dotitems.move(fromOffsets: from, toOffset: to)
}
func addItem(dotName: String, dotNumber: String, selection: String){
let newItem = DotItem(dotName: dotName, dotNumber: dotNumber, selection: selection)
dotitems.append(newItem)
print(newItem)
}
func saveItem() {
if let encodedData = try? JSONEncoder().encode(dotitems) {
UserDefaults.standard.set(encodedData, forKey: dotitemsKey)
}
}
}
This is the view that I'm entering the data for each item
struct SwiftUIView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#AppStorage("dotApiKey") var selectedDotApi: String = ""
#State var dotName:String
#State var dotNumber:String
#State var selection:String
#State var triggerSelection:String
#State var selectedColor = Color.black
#State var searchText:String
#State var plugUsername:String
#State var plugPassword:String
#ObservedObject var vm = getDeviceNames()
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#StateObject var camera = CameraModel()
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField("Name", text: $dotName)
TextField("Number", text: $dotNumber)
Picker(selection: $selection, label: Text("Discover Plug")) {
ForEach(vm.dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
and this is the view that I'm trying to show the data when any list item is selected which is basically the same as above except in a few places
struct EditView: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var listViewModel: ListViewModel
#ObservedObject var vm = getDeviceNames()
#State var dataSet = [Result]()
#State var dotName:String = ""
#State var dotNumber:String = ""
#State var selection:String = ""
#State var triggerSelection:String = ""
#State var selectedColor = Color.black
#State var searchText:String = ""
#State var plugUsername:String = ""
#State var plugPassword:String = ""
#State var triggerDot:Bool = false
#State var toggleOnOff:Bool = false
#State var toggleLightColor:Bool = false
#State var isSearching:Bool = false
#Binding var show : Bool
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
TextField(dotName, text: $dotName)
TextField(dotNumber, text: $dotNumber)
Picker(selection: $selection, label: Text("Discorver Plug")) {
ForEach(dataSet, id:\.self) { item in
Text(item.Device).tag(item.Device)
}
}
Toggle(isOn: $isSearching, label: {
Text("Have smart plugs?")
})
if isSearching {
HStack{
Text("Casa")
Spacer()
Button(action: {
SwiftUIView(dotName: dotName, dotNumber: dotNumber, selection: selection, triggerSelection: triggerSelection, searchText: searchText, plugUsername: plugUsername, plugPassword: plugPassword, show: $show).sendPlugDict()}, label: {
Text("Login")
})
}
TextField("Username", text: $plugUsername)
TextField("Password", text: $plugPassword)
}
}
Section {
Toggle(isOn: $toggleOnOff, label: {
Text("On/Off")
})
}
Section {
Toggle(isOn: $toggleLightColor, label: {
Text("Light Color")
})
if toggleLightColor {
ColorPicker("Choose Light Color", selection: $selectedColor)
}
}
Section {
if listViewModel.dotitems.isEmpty == false {
Toggle(isOn: $triggerDot, label: {
Text("Add a DOT to trigger")
})
if triggerDot {
Picker(selection: $triggerSelection, label: Text("Select DOT")) {
ForEach(listViewModel.dotitems, id:\.dotId){ dotitem in
DotItemListView(dotitem: dotitem)
}
}
}
}
}
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
}
I know this is a lot to go through but any help would be greatly appreciated
use #Binding on the view that you want to pass state to .
View1 :
#State var a = true
View2(a: $a)
View2 :
#Binding var a : Bool
for passing data globally use #EnvoirmentObject :
example :
#main
struct YourApp: App {
#StateObject var session = Session()
var body: some Scene {
WindowGroup {
SplashView()
.environmentObject(session)
}
}
}
Session :
class Session: ObservableObject {
/// user signin state
#Published var isSignedIn : Bool = false
}
View1 ,View2 , View3 .... :
struct SplashView: View {
#EnvironmentObject var session : Session
var body: some View {
VStack{
SignInView()
.opacity(session.isSignedIn ? 0:1)
}
.background(Color.background.ignoresSafeArea())
}
}

SwiftUI List single selectable item

I'm trying to create a List and allow only one item to be selected at a time. How would I do so in a ForEach loop? I can select multiple items just fine, but the end goal is to have only one checkmark in the selected item in the List. It may not even be the proper way to handle what I'm attempting.
struct ContentView: View {
var body: some View {
NavigationView {
List((1 ..< 4).indices, id: \.self) { index in
CheckmarkView(index: index)
.padding(.all, 3)
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
//.environment(\.editMode, .constant(.active))
}
}
}
struct CheckmarkView: View {
let index: Int
#State var check: Bool = false
var body: some View {
Button(action: {
check.toggle()
}) {
HStack {
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check {
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
}
}
}
}
}
You'll need something to store all of the states instead of storing it per-checkmark view, because of the requirement to just have one thing checked at a time. I made a little example where the logic is handled in an ObservableObject and passed to the checkmark views through a custom Binding that handles checking/unchecking states:
struct CheckmarkModel {
var id = UUID()
var state = false
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
func singularBinding(forIndex index: Int) -> Binding<Bool> {
Binding<Bool> { () -> Bool in
self.checkmarks[index].state
} set: { (newValue) in
self.checkmarks = self.checkmarks.enumerated().map { itemIndex, item in
var itemCopy = item
if index == itemIndex {
itemCopy.state = newValue
} else {
//not the same index
if newValue {
itemCopy.state = false
}
}
return itemCopy
}
}
}
}
struct ContentView: View {
#ObservedObject var state = StateManager()
var body: some View {
NavigationView {
List(Array(state.checkmarks.enumerated()), id: \.1.id) { (index, item) in //<-- here
CheckmarkView(index: index + 1, check: state.singularBinding(forIndex: index))
.padding(.all, 3)
}
.listStyle(PlainListStyle())
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct CheckmarkView: View {
let index: Int
#Binding var check: Bool //<-- Here
var body: some View {
Button(action: {
check.toggle()
}) {
HStack {
Image("Image-\(index)")
.resizable()
.frame(width: 70, height: 70)
.cornerRadius(13.5)
Text("Example-\(index)")
Spacer()
if check {
Image(systemName: "checkmark")
.resizable()
.frame(width: 12, height: 12)
}
}
}
}
}
What's happening:
There's a CheckmarkModel that has an ID for each checkbox, and the state of that box
StateManager keeps an array of those models. It also has a custom binding for each index of the array. For the getter, it just returns the state of the model at that index. For the setter, it makes a new copy of the checkbox array. Any time a checkbox is set, it unchecks all of the other boxes. I also kept your original behavior of allowing nothing to be checked
The List now gets an enumeration of the state.checkmarks -- using enumerated lets me keep your previous behavior of being able to pass an index number to the checkbox view
Inside the ForEach, the custom binding from before is created and passed to the subview
In the subview, instead of using #State, #Binding is used (this is what the custom Binding is passed to)
List {
ForEach(0 ..< RemindTimeType.allCases.count) {
index in CheckmarkView(title:getListTitle(index), index: index, markIndex: $markIndex)
.padding(.all, 3)
}.listRowBackground(Color.clear)
}
struct CheckmarkView: View {
let title: String
let index: Int
#Binding var markIndex: Int
var body: some View {
Button(action: {
markIndex = index
}) {
HStack {
Text(title)
.foregroundColor(Color.white)
.font(.custom(FontEnum.Regular.fontName, size: 14))
Spacer()
if index == markIndex {
Image(systemName: "checkmark.circle.fill")
.foregroundColor(Color(hex: 0xe6c27c))
}
}
}
}
}
We can benefit from binding collections of Swift 5.5.
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable {
var id = UUID()
var state = false
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(), CheckmarkModel(), CheckmarkModel(), CheckmarkModel()]
}
struct SingleSelectionList<Content: View>: View {
#Binding var items: [CheckmarkModel]
#Binding var selectedItem: CheckmarkModel?
var rowContent: (CheckmarkModel) -> Content
#State var previouslySelectedItemNdx: Int?
var body: some View {
List(Array($items.enumerated()), id: \.1.id) { (ndx, $item) in
rowContent(item)
.modifier(CheckmarkModifier(checked: item.id == self.selectedItem?.id))
.contentShape(Rectangle())
.onTapGesture {
if let prevIndex = previouslySelectedItemNdx {
items[prevIndex].state = false
}
self.selectedItem = item
item.state = true
previouslySelectedItemNdx = ndx
}
}
}
}
struct CheckmarkModifier: ViewModifier {
var checked: Bool = false
func body(content: Content) -> some View {
Group {
if checked {
ZStack(alignment: .trailing) {
content
Image(systemName: "checkmark")
.resizable()
.frame(width: 20, height: 20)
.foregroundColor(.green)
.shadow(radius: 1)
}
} else {
content
}
}
}
}
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selectedItem: CheckmarkModel?
var body: some View {
VStack {
Text("Selected Item: \(selectedItem?.id.description ?? "Select one")")
Divider()
SingleSelectionList(items: $state.checkmarks, selectedItem: $selectedItem) { item in
HStack {
Text(item.id.description + " " + item.state.description)
Spacer()
}
}
}
}
}
A bit simplified version
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selection: CheckmarkModel.ID?
var body: some View {
List {
ForEach($state.checkmarks) { $item in
SelectionCell(item: $item, selectedItem: $selection)
.onTapGesture {
if let ndx = state.checkmarks.firstIndex(where: { $0.id == selection}) {
state.checkmarks[ndx].state = false
}
selection = item.id
item.state = true
}
}
}
.listStyle(.plain)
}
}
struct SelectionCell: View {
#Binding var item: CheckmarkModel
#Binding var selectedItem: CheckmarkModel.ID?
var body: some View {
HStack {
Text(item.id.description + " " + item.state.description)
Spacer()
if item.id == selectedItem {
Image(systemName: "checkmark")
.foregroundColor(.accentColor)
}
}
}
}
A version that uses internal List's selected mark and selection:
import SwiftUI
struct CheckmarkModel: Identifiable, Hashable {
var name: String
var state: Bool = false
var id = UUID()
}
class StateManager : ObservableObject {
#Published var checkmarks = [CheckmarkModel(name: "Name1"), CheckmarkModel(name: "Name2"), CheckmarkModel(name: "Name3"), CheckmarkModel(name: "Name4")]
}
struct ContentView: View {
#ObservedObject var state = StateManager()
#State private var selection: CheckmarkModel.ID?
#State private var selectedItems = [CheckmarkModel]()
var body: some View {
VStack {
Text("Items")
List($state.checkmarks, selection: $selection) { $item in
Text(item.name + " " + item.state.description)
}
.onChange(of: selection) { s in
for index in state.checkmarks.indices {
if state.checkmarks[index].state == true {
state.checkmarks[index].state = false
}
}
selectedItems = []
if let ndx = state.checkmarks.firstIndex(where: { $0.id == selection}) {
state.checkmarks[ndx].state = true
selectedItems = [state.checkmarks[ndx]]
print(selectedItems)
}
}
.environment(\.editMode, .constant(.active))
Divider()
List(selectedItems) {
Text($0.name + " " + $0.state.description)
}
}
Text("\(selectedItems.count) selections")
}
}

NavigationView inside NavigationView SwiftUI

How can I remove navigationBar inside navigationView.When I use navigate view inside another navigationview it show me another navigationbar and put space from navigationBar.How can I solve this problem . I tried to use navigationBarHidden or NavigationBarTitle (displaymode : .inline) but it didn't work.Its works when I use for one NavigationView but inside another navigationView its not working.
struct ShowCaseView : View {
var productList = ShowCaseViewModel()
#State var cancellable = Set<AnyCancellable>()
#State var productListData : ShowCaseDataResponse?
#State var isAnimating : Bool = true
#State var showCaseData : [ShowCaseData] = []
#State var isOpened : Bool = true
var body: some View {
ZStack{
NavigationView{
ScrollView {
VStack {
if productListData?.success == true {
ForEach(showCaseData , id:\.id) { data in
if data.isHeaderVisible == true {
Text(data.name ?? "")
.font(.system(size: 18))
.bold()
}
ListTypeShow(data: data)
}
}
}
}
}.navigationBarTitle("",displayMode: .inline)
ActivityIndicator(isAnimating: $isAnimating)
}
.onAppear {
if isOpened == true {
getStoreIndex()
}
}
}
If parent view of ShowCaseView already has NavigationView then you don't need another one in ShowCaseView, ie.
struct ShowCaseView : View {
var productList = ShowCaseViewModel()
#State var cancellable = Set<AnyCancellable>()
#State var productListData : ShowCaseDataResponse?
#State var isAnimating : Bool = true
#State var showCaseData : [ShowCaseData] = []
#State var isOpened : Bool = true
var body: some View {
ZStack{
// NavigationView{ // << remove this one !!

How to Transmit a View Entry Count to a Class Method

I'm having trouble with usage of a count of the number of entries in a view. I especially need to know when there are no entries in the view. I have placed debug code in the view below and the view count currants.curItem.countis updating as expected. The count status in checkForUpdates() doesn't follow the view above.
If I recall correctly I should be using #EnvironmentObject or #ObservedObject only in a view. I really need some kind of global variable that I can pass to the method checkForUpdates. It is crashing when count in checkForUpdates() is nonzero when in the view it is actually zero. It also crashes in checkForUpdates() with the error Fatal error: No ObservableObject of type Currencies found. A View.environmentObject(_:) for Currencies may be missing as an ancestor of this view.
struct manCurView: View {
#EnvironmentObject var currants: Currants
var body: some View {
List {
ForEach(currants.curItem, id: \.id) { item in
HStack {
Text(item.curCode)
.frame(width: 100, alignment: .center)
Text(item.cunName)
}
.font(.subheadline)
}
.onDelete(perform: removeItems)
}
.navigationBarTitle(Text("Manage Working Blocks"), displayMode: .inline)
HStack {
NavigationLink(destination: addCurView()) {Text("Add Working Blocks").fontWeight(.bold)}
.font(.title2)
.disabled(currants.curItem.count > 7)
Here is how the data is stored for the view above
struct CurItem: Codable, Identifiable {
var id = UUID()
var cunName: String
var curName: String
var curCode: String
var curSymbol: String
var curRate: Double
}
class Currants: ObservableObject {
#Published var curItem: [CurItem]
}
And here is the class and method where I would like to use count from the view manCurView
class BlockStatus: ObservableObject {
#EnvironmentObject var globalCur : Currants
#ObservedObject var netStatus : TestNetStatus = TestNetStatus()
func checkForUpdates() -> (Bool) {
if netStatus.connected == true {
if globalCur.curItem.count > 0 {
Without a minimal reproducible example it is very difficult to give you exact code but you can try something like the code below in your manCurView
#StateObject var blockStatus: BlockStatus = BlockStatus()
.onChange(of: currants.curItem.count, perform: { value in
print("send value from here")
blockStatus.arrayCount = value
})
And adding the code below to BlockStatus
#Published var arrayCount: Int = 0{
didSet{
//Call your method here
}
}
Look at the code below.
import SwiftUI
import Combine
struct CurItem: Codable, Identifiable {
var id = UUID()
}
class Currants: ObservableObject {
#Published var curItem: [CurItem] = [CurItem(), CurItem(), CurItem(), CurItem()]
}
class TestNetStatus: ObservableObject {
static let sharedInstance = TestNetStatus()
#Published var connected: Bool = false
init() {
//Simulate changes in connection
Timer.scheduledTimer(withTimeInterval: 10, repeats: true){ timer in
self.connected.toggle()
}
}
}
class BlockStatus: ObservableObject {
#Published var arrayCount: Int = 0{
didSet{
checkForUpdates()
}
}
#Published var checkedForUpdates: Bool = false
var netStatus : TestNetStatus = TestNetStatus.sharedInstance
//private var cancellable: AnyCancellable?
init() {
//Maybe? if you want to check upon init.
//checkForUpdates()
//Something like the code below is also possible but with 2 observed objects the other variable could be outdated
// cancellable = netStatus.objectWillChange.sink { [weak self] in
// self?.checkForUpdates()
// }
}
func checkForUpdates() {
if netStatus.connected == true {
if arrayCount > 0 {
checkedForUpdates = true
}else{
checkedForUpdates = false
}
}else{
checkedForUpdates = false
}
}
}
struct ManCurView: View {
#StateObject var currants: Currants = Currants()
#StateObject var blockStatus: BlockStatus = BlockStatus()
#StateObject var testNetStatus: TestNetStatus = TestNetStatus.sharedInstance
var body: some View {
List {
Text("checkedForUpdates = " + blockStatus.checkedForUpdates.description).foregroundColor(blockStatus.checkedForUpdates ? Color.green : Color.red)
Text("connected = " + blockStatus.netStatus.connected.description).foregroundColor(blockStatus.netStatus.connected ? Color.green : Color.red)
ForEach(currants.curItem, id: \.id) { item in
HStack {
Text(item.id.uuidString)
.frame(width: 100, alignment: .center)
Text(item.id.uuidString)
}
.font(.subheadline)
}
//Replaced with toolbar button for sample
//.onDelete(perform: removeItems)
//When the array count changes
.onChange(of: currants.curItem.count, perform: { value in
blockStatus.arrayCount = value
})
//Check when the networkStatus changes
.onChange(of: testNetStatus.connected, perform: { value in
//Check arrayCount
if blockStatus.arrayCount != currants.curItem.count{
blockStatus.arrayCount = currants.curItem.count
}else{
blockStatus.checkForUpdates()
}
})
}
.navigationBarTitle(Text("Manage Working Blocks"), displayMode: .inline)
//Replaced addCurView call with toolbar button for sample
.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing, content: {
Button("add-currant", action: {
currants.curItem.append(CurItem())
})
})
ToolbarItem(placement: .navigationBarLeading, content: {
Button("delete-currant", action: {
if currants.curItem.count > 0{
currants.curItem.removeFirst()
}
})
})
})
}
}
Here is ContentView: Notice in the menu that because this is a view I can use count directly to disable entry input. Down in getData() notice that I'm calling blockStatus.checkForUpdates() to determine if is OK to call the API. A fault will occur if currants.curItem.count = 0
I just realized that technically getData() is part of the ContentView so I can change the call below to if blockStatus.checkForUpdates() == true && currants.curItem.count != 0 {
I'm going to spend some time studying your suggestions above to see if I could use this in the future.
So thanks for all the help by looking into this. I wasn't aware of the suggestions on code displayed on Stackoverflow. I'll be sure to follow those guidelines in the future. Galen
import SwiftUI
import CoreData
import Combine
struct ContentView: View {
#EnvironmentObject var userData: UserData
#EnvironmentObject var currants: Currants
#EnvironmentObject var blockStatus: BlockStatus
var body: some View {
NavigationView {
VStack (alignment: .center) {
Text("Title")
.font(.title)
.fontWeight(.bold)
Spacer()
Group {
NavigationLink(destination: entryView()) {Text("Entry")}
.disabled(currants.curItem.count == 0)
Spacer()
NavigationLink(destination: totalView()) {Text("View Totals")}
Spacer()
NavigationLink(destination: listView()) {Text("View Entries")}
Spacer()
NavigationLink(destination: xchView()) {Text("View Dates")}
}
Rectangle()
.frame(height: 130)
.foregroundColor(Color.white)
}
.font(.title2)
.navigationBarItems(leading: NavigationLink (destination: settingsView()) {
Image(systemName: "gear")
.foregroundColor(.gray)
.font(.system(.title3))
}, trailing: NavigationLink( destination: aboutView()) {
Text("About")
})
.onAppear(perform: getData)
}
}
func getData() {
// check criteria for updating data once daily
if blockStatus.checkForUpdates() == true {
print(" doing update")
---- API HERE -----
}.resume()
}
}
}

#State initial value not resetting variable on init()

I have a segmented control that I am using as a tabs in the toolbar of my app linked to the selectedTab variable. when I switch tabs the accounts list changes but it does not reset the lines list. I tried using initialValue on the selected to make sure it reset to 0 but this did not effect it. I tried a print in the init to make sure that the value of selected was 0, after the init. it was every time but it still did not refresh the lines foreach list.
What am I missing?
import SwiftUI
import SQLite3
struct ContentView:View {
#EnvironmentObject var shared:SharedObject
var body: some View {
VStack {
if shared.selectedTab == 0 {
LedgerView(ledger: .Accounts)
} else if shared.selectedTab == 1 {
LedgerView(ledger: .Budgets)
} else if shared.selectedTab == 2 {
ReportsView()
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct LedgerView:View {
#EnvironmentObject var shared:SharedObject
let ledger:LedgerType
#State var selected:Int = 0
init(ledger:LedgerType) {
self.ledger = ledger
self._selected = State(initialValue: 0)
}
var body:some View {
HStack {
VStack(alignment: HorizontalAlignment.leading) {
ForEach(shared.accounts.filter({$0.ledger == ledger})) { account in
Text(account.name)
.background(account.id == self.selected ? Color.accentColor : Color.clear)
.onTapGesture {self.selected = account.id}
}
}
Divider()
VStack(alignment: HorizontalAlignment.leading) {
ForEach(shared.journalLines.filter({$0.accountID == selected})) { line in
Text("Line#\(line.id)")
}
}
}
}
}
struct ReportsView:View {
var body:some View {
Text("Under Construction ...")
}
}
class SharedObject:ObservableObject {
#Published var accounts:[Account] = []
#Published var journalLines:[JournalLine] = []
#Published var selectedTab:Int = 0
init() {
loadData()
}
}
enum LedgerType:Int {
case Accounts=0,Budgets=1
var name:String {
switch(self) {
case .Accounts: return "Accounts"
case .Budgets: return "Budgets"
}
}
}
struct Account:Identifiable {
var id:Int
var name:String
var ledger:LedgerType
}
struct Payee:Identifiable {
var id:Int
var name:String
}
struct Journal:Identifiable {
var id:Int
var date:Date
var payeeID:Int
var memo:String?
}
struct JournalLine:Identifiable {
var id:Int
var journalID:Int
var accountID:Int
var amount:Double
}
edit abridged demo code to try to isolate the problem
import SwiftUI
struct ContentView: View {
#EnvironmentObject var shared:SharedObject
var body: some View {
VStack {
Picker(selection: $shared.selectedTab, label: Text("")) {
Text("Accounts").tag(0)
Text("Budgets").tag(1)
}.pickerStyle(SegmentedPickerStyle())
Divider()
if shared.selectedTab == 0 || shared.selectedTab == 1 {
LedgerView()
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct LedgerView:View {
#State var selected:Int = 0
init() {
self._selected = State(initialValue: 0)
print("LedgerView.init()")
}
var body:some View {
VStack(alignment: HorizontalAlignment.leading) {
Text("Selected: \(selected)")
Picker(selection: $selected, label: Text("")) {
Text("Account#1").tag(1)
Text("Account#2").tag(2)
Text("Account#3").tag(3)
}
}
}
}
class SharedObject: ObservableObject {
#Published var selectedTab:Int = 0
}
Based on your recent comment, what you need is #Binding and not #State. To explain: The struct LedgerView is just an extract of the picker in a separate view. When you call LedgerView(), this doesn't instantiate a new object. It simply adds the picker view in that place. Therefore, when you need the picker to reset on switching tabs, you need to use binding to reset the picker. Here's the working code. Hope it helps.
struct ContentView: View {
#EnvironmentObject var shared:SharedObject
#State var selected: Int = 0
var body: some View {
VStack {
Picker(selection: $shared.selectedTab, label: Text("")) {
Text("Accounts").tag(0)
Text("Budgets").tag(1)
}.pickerStyle(SegmentedPickerStyle())
Divider()
if shared.selectedTab == 0 || shared.selectedTab == 1 {
LedgerView(selected: $selected)
}
Spacer()
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onReceive(shared.$selectedTab) { newValue in
self.selected = 0
}
}
}
struct LedgerView:View {
#Binding var selected: Int
var body:some View {
VStack(alignment: HorizontalAlignment.leading) {
Text("Selected: \(selected)")
Picker(selection: $selected, label: Text("")) {
Text("Account#1").tag(1)
Text("Account#2").tag(2)
Text("Account#3").tag(3)
}
}
}
}
[Earlier answer containing alternate solution]
I've modified your code to make the lines work. I've added sample data to get the code to work. I doubt if the problem is with your data. Also I have modified the enum LedgerType to make it iterable. Here's the working code.
I've modified the following in code:
Removed passing ledgerType to LedgerView as the source of truth is
#EnvironmentObject var shared: SharedObject
Added code to select the first account by default when switching tabs. This refreshes the lines when switching between tabs. See the code in .onReceive
Hope this helps. If you need anything, let me know.
struct ContentView:View {
#EnvironmentObject var shared: SharedObject
var body: some View {
VStack {
Picker(selection: $shared.selectedTab, label: Text("")) {
ForEach(0 ..< LedgerType.allCases.count) { index in
Text(LedgerType.allCases[index].rawValue).tag(index)
}
}
.pickerStyle(SegmentedPickerStyle())
if shared.selectedTab == 0 || shared.selectedTab == 1 {
LedgerView()
} else if shared.selectedTab == 2 {
ReportsView()
}
Spacer()
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct LedgerView:View {
#EnvironmentObject var shared:SharedObject
#State var selected: Int = 0
var body:some View {
HStack {
VStack(alignment: HorizontalAlignment.leading) {
ForEach(shared.accounts.filter({ $0.ledger == LedgerType.allCases[shared.selectedTab] })) { account in
Text(account.name)
.background(account.id == self.selected ? Color.accentColor : Color.clear)
.onTapGesture {self.selected = account.id}
}
}
Divider()
VStack(alignment: HorizontalAlignment.leading) {
ForEach(shared.journalLines.filter({$0.accountID == selected})) { line in
Text("Line#\(line.id)")
}
}
}
.onReceive(shared.$selectedTab) { newValue in
if let id = self.shared.getInitialAccountId(tabIndex: newValue) {
self.selected = id
}
}
}
}
struct ReportsView:View {
var body:some View {
Text("Under Construction ...")
}
}
class SharedObject:ObservableObject {
#Published var accounts:[Account] = []
#Published var journalLines:[JournalLine] = []
#Published var selectedTab:Int = 0
func getInitialAccountId(tabIndex: Int) -> Int? {
if tabIndex == 0 {
return accounts.filter({
$0.ledger == LedgerType.Accounts
}).first?.id
}
else if tabIndex == 1 {
return accounts.filter({
$0.ledger == LedgerType.Budgets
}).first?.id
}
else {
return accounts.filter({
$0.ledger == LedgerType.Reports
}).first?.id
}
}
init() {
accounts = [
Account(id: 1, name: "Sales", ledger: .Accounts),
Account(id: 2, name: "Purchase", ledger: .Accounts),
Account(id: 3, name: "Forecast", ledger: .Budgets)
]
journalLines = [
// Line for sales
JournalLine(id: 1, journalID: 10, accountID: 1, amount: 200),
JournalLine(id: 2, journalID: 20, accountID: 1, amount: 400),
// Line for purchase
JournalLine(id: 3, journalID: 30, accountID: 2, amount: 600),
JournalLine(id: 4, journalID: 40, accountID: 2, amount: 800)
]
}
}
enum LedgerType: String, CaseIterable {
case Accounts = "Accounts"
case Budgets = "Budgets"
case Reports = "Reports"
}
struct Account:Identifiable {
var id:Int
var name:String
var ledger:LedgerType
}
struct Payee:Identifiable {
var id:Int
var name:String
}
struct Journal:Identifiable {
var id:Int
var date:Date
var payeeID:Int
var memo:String?
}
struct JournalLine:Identifiable {
var id:Int
var journalID:Int
var accountID:Int
var amount:Double
}
shared.accounts doesn't exist. you have never initialized it
[...]
if shared.selectedTab == 0 {
LedgerView(ledger: .Accounts).environmentObject(SharedObject())
} else if shared.selectedTab == 1 {
LedgerView(ledger: .Budgets).environmentObject(SharedObject())
[...]
Edit: Make sure your SceneDelgate also initiates SharedObject otherwise it wont work on physical/simulator device
--
Edit: After running your code and you confirmed you are loading, I ran your code with no change and I don't see any issue, can you look at the gif below and comment what's wrong in what I am seeing?