Group Group Group Group Group Group Group Group Group

Your Second iOS and SwiftUI App · Adding to your Model | raywenderlich.com


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5662524-your-second-ios-and-swiftui-app/lessons/12

Hi Jessy, I just found a small bug, maybe related to Swift UI, after adding a task and dismiss the sheet, if I click again on the + button, nothing happens, the button is not clickable anymore, however if I make the list scroll then try again to click the + button, it works, the modal sheet is presented. I did not try on a real device, it could be also a simulator bug …

thanks

2 Likes

Thanks! Someone spotted it on a later video, so I’m aware: Your Second iOS and SwiftUI App · Observable Objects | raywenderlich.com

I’m seeing it both on-device and off, with Xcode 11.1 and 11.2. No word from Apple yet.

I found a little more with this bug, on my device (iOS 13.1.3) the button does work, but only if you tap a few times, as if there is some delay in the main screen getting focus. Additionally to the Edit / Done button already mentioned, if you interact with the scrollview first it will also work.

Changing sheet to popover makes no different.

I also tried using a callback function and passing {self.modalIsPresented = false} as an alternative to the @Environment code, but this also has the same issue.

This code gives me error with following message:
“Cannot use mutating member on immutable value: ‘self’ is immutable”

The only way I know of that that would show up is if you tried to have TaskStore be a structure instead of a class. (See the end of Episode 2)

If that’s not it, upload your project and I’ll have a look!

Got it.
You are correct. i changed TaskStore to class and error has gone. Thanks.
Other solution is to write my own method in struct and mark as mutating, right?

No, that wouldn’t work because we’re already dealing with a mutable property. The issue comes down to trying to modify an immutable copy of a struct, which has no access to the scope of the original.

SwiftUI’s easiest solutions for this are found in State and Binding, but I haven’t found those to be usable for delivering bound values to modal views. ObservedObject and Environment are the next place to go. Let me know if you find out a way to create the final version of TaskList without reference types!

Are there any good news? I have got just the same bug :confused:

@andreysvx Do you still have issues with this?

everything is ok now) thank you)

@jessycatterwaul When you said we have to use environment here, my first thought was just to pass down a function via dependency injection that would close the modal by changing the state variable in ContentView.

Is there are reason environment is better than just passing down some functions?

1 Like

I wouldn’t say it’s better, but I do think the environment is a simpler solution, at least for this course’s intended audience, who may never have DI’d closures before. Personally, I’ve found myself using a lot more non-closure properties in SwiftUI, versus UIKit, somewhat because of the difficulty of referring to the right scope, which comes from all the views being structs. I’d love to see a reworked example, though, if you’d care to share one!

1 Like

Alright! While I can see your viewpoint, I think I prefer closures because they eliminate some of the “swiftui magic” that makes it a little hard for me to read.

Here’s my example with closures:

  • ContentView.swift:
struct ContentView: View {

	var taskStore: TaskStore

	@State var modalIsPresented = false
	func closeModal() {
		modalIsPresented = false
	}
	func openModal() {
		modalIsPresented = true
	}

	var body: some View {
		NavigationView {
			List(taskStore.tasks) { task in
				Text("\(task.name)")
			}.navigationBarTitle("Tasks").navigationBarItems(trailing: Button(action: openModal) {
				Image(systemName: "plus")
			}).sheet(isPresented: $modalIsPresented) {
				NewTaskView(taskStore: self.taskStore, closeModal: self.closeModal)
			}
		}

	}
}
  • NewTaskView.swift:
var taskStore: TaskStore

	let closeModal: () -> ()

	@State var text: String = ""

	var body: some View {
		NavigationView {
			Form {
				TextField("New Task", text: $text)

				Button(action: { self.taskStore.tasks.append(Task(name: self.text)) },
				       label: { Text("Add to List") })
					.disabled(text.isEmpty)

			}
				.navigationBarTitle("New Task", displayMode: .inline)
				.navigationBarItems(leading: Button(action: closeModal) {
					Image(systemName: "xmark")
				}, trailing: Button(action: { }) {
					Image(systemName: "plus.circle")
				})
		}

	}
1 Like

@jessycatterwaul Thank for your thorough response on my last question, but after continuing the video, I have another.

At the end you expose the “bug” where the ContentView only gets re-rendered with the new data because the modal closes, but when the ContentView updates the TaskList itself, it will not re-render.

I am wondering why this is?

Is it because:

  1. We’re don’t even have a @State tag on the tasklist?
  2. TaskList is a class and references cannot be tracked by @State?
  3. Because we’re accessing a property and property updates canno be tracked by @State?
  4. Is it because an Array is class and the update cannot be tracked by @State?

Could we have also solved this problem by making TaskList a struct, and passing down the array of tasks and some functions to append tasks? Is the reason not to do this because of extra complexity?

Would appreciate some guidance :smile:

I think your intuition was good. You could come up with a struct-based solution, but I think it would necessarily have to be more complex.

Hopefully the next episode cleared things up, in regards to reference types?

I got the same bug with the plus showing up today. Looks like my Xcode is updated too. Do you think I am missing something else ?

No. Even in Xcode 11.4 beta 3, there’s no change. :frowning:

Is there a solution to make it work on the mobile. It still doesn’t work, and it seems that it does work for some people. Can you post the solution?