macOS - Error while passing information through UI View - swift

I am trying to pass information from one SwiftUI View (let's say View1) to another one (View2) using NavigationLink. I have found this article, but when I try to run it I get the error "Cannot find type 'Event' in scope":
Here is my code for View1:
#available(OSX 11.0, *)
struct Mods_UI: View {
let PassMe = "Hello!"
var body: some View {
NavigationView {
NavigationLink(destination: MarkdownView(item: String(PassMe))) {
Label("Hello World!")
}
}
}
}
Here is my code for View2:
struct MarkdownView: View {
let item: Event
var body: some View {
Text(String(item))
}
}
I have no idea what is going wrong. If more information is needed, please tell me.
Swift Version: Latest (5.3.3)
Xcode Version: Latest (12.4)

In the page you linked to, they have a custom type called Event, probably defined as a struct somewhere in their code that was not included in the post.
You, however are just trying to pass a String around. You can do this:
#available(OSX 11.0, *)
struct Mods_UI: View {
let passMe = "Hello!"
var body: some View {
NavigationView {
NavigationLink(destination: MarkdownView(item: passMe)) {
Label("Hello World!")
}
}
}
}
struct MarkdownView: View {
var item: String
var body: some View {
Text(item)
}
}

Related

How I can help struct infer View type values in a closure that feeds the needed type in SwiftUI?

The code below works:
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.customViewModifier(modifier: { view in CustomModifier(content: { view } )} )
}
}
struct CustomModifier<Content: View>: View {
let content: () -> Content
var body: some View {
content()
.foregroundColor(.red)
}
}
extension View {
func customViewModifier<ContentModifier: View>(modifier: (Self) -> ContentModifier) -> some View {
return modifier(self)
}
}
My goal is to be able the code below. Currently, Xcode does not help me to fix the error.
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.customViewModifier(modifier: CustomModifier)
}
}
Is there a way around to make my goal possible?
here was my try to solve the issue:
extension View {
func customViewModifier2<ContentModifier: View>(modifier: (Self) -> ((Self) -> ContentModifier)) -> some View {
return modifier(self)
}
}
Error:
Type '(Self) -> ContentModifier' cannot conform to 'View'
The final real goal is not clear ('cause it is really better to use ViewModifier based approach), but if you want to use a type as argument it can be like the following
*compiled with Xcode 13.2 / iOS 15.2
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.customViewModifier(modifier: CustomModifier.self) // << type !!
}
}
extension View {
func customViewModifier(modifier: CustomModifier<Self>.Type) -> some View {
return modifier.init(content: { self })
}
}

SwiftUI macOS NavigationView - onChange(of: Bool) action tried to update multiple times per frame

I'm seeing onChange(of: Bool) action tried to update multiple times per frame warnings when clicking on NavigationLinks in the sidebar for a SwiftUI macOS App.
Here's what I currently have:
import SwiftUI
#main
struct BazbarApp: App {
#StateObject private var modelData = ModelData()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(modelData)
}
}
}
class ModelData: ObservableObject {
#Published var myLinks = [URL(string: "https://google.com")!, URL(string: "https://apple.com")!, URL(string: "https://amazon.com")!]
}
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
#State private var selected: URL?
var body: some View {
NavigationView {
List(selection: $selected) {
Section(header: Text("Bookmarks")) {
ForEach(modelData.myLinks, id: \.self) { url in
NavigationLink(destination: DetailView(selected: $selected) ) {
Text(url.absoluteString)
}
.tag(url)
}
}
}
.onDeleteCommand {
if let selected = selected {
modelData.myLinks.remove(at: modelData.myLinks.firstIndex(of: selected)!)
}
selected = nil
}
Text("Choose a link")
}
}
}
struct DetailView: View {
#Binding var selected: URL?
var body: some View {
if let selected = selected {
Text("Currently selected: \(selected)")
}
else {
Text("Choose a link")
}
}
}
When I alternate clicking on the second and third links in the sidebar, I eventually start seeing the aforementioned warnings in my console.
Here's a gif of what I'm referring to:
Interestingly, the warning does not appear when alternating clicks between the first and second link.
Does anyone know how to fix this?
I'm using macOS 12.2.1 & Xcode 13.2.1.
Thanks in advance
I think the issue is that both the List(selection:) and the NavigationLink are trying to update the state variable selected at once. A List(selection:) and a NavigationLink can both handle the task of navigation. The solution is to abandon one of them. You can use either to handle navigation.
Since List look good, I suggest sticking with that. The NavigationLink can then be removed. The second view under NavigationView is displayed on the right, so why not use DetailView(selected:) there. You already made the selected parameter a binding variable, so the view will update if that var changes.
struct ContentView: View {
#EnvironmentObject var modelData: ModelData
#State private var selected: URL?
var body: some View {
NavigationView {
List(selection: $selected) {
Section(header: Text("Bookmarks")) {
ForEach(modelData.myLinks, id: \.self) { url in
Text(url.absoluteString)
.tag(url)
}
}
}
.onDeleteCommand {
if let selected = selected {
modelData.myLinks.remove(at: modelData.myLinks.firstIndex(of: selected)!)
}
selected = nil
}
DetailView(selected: $selected)
}
}
}
I can recreate this problem with the simplest example I can think of so my guess is it's an internal bug in NavigationView.
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink("A", destination: Text("A"))
NavigationLink("B", destination: Text("B"))
NavigationLink("C", destination: Text("C"))
}
}
}
}

SwiftUI - Xcode - Inferring type not possible for VStack

I am trying to create a simple master/detail app in Xcode.
I want that the detail view is
struct EditingView: View
{
var body: some View {
var mainVertical: VStack = VStack() //error here
{
var previewArea: HStack = HStack()
{
var editorButton: Button = Button()
//the same with return editorButton
// I have to add other controls, like a WKWebView
}
return previewArea
//this is a simple version, layout will have other stacks with controls inside
}
return mainVertical
}
}
but I get
Generic parameter 'Content' could not be inferred
The IDE offers me to fix but if I do that, it writes a generic type I have to fill but then other errors come, f.i. if I put AnyView o TupleView.
I would like that it infers everything, what is wrong that it cannot understand?
In SwiftUI you usually don't need to reference your controls. You can apply modifiers to them directly in the view.
This is the preferred way:
struct ContentView: View {
var body: some View {
VStack {
HStack {
Button("Click me") {
// some action
}
}
}
.background(Color.red) // modify your `VStack`
}
}
Alternatively if needed you can extract controls as separate variables:
struct ContentView: View {
var body: some View {
let hstack = HStack {
button
}
return VStack {
hstack
}
}
var button: some View {
Button("Click me") {
// some action
}
}
}
But in the end I definitely recommend you read Apple SwiftUI tutorials

SwiftUI #ObservedObject viewmodel in detail-view of List never released [duplicate]

This question already has answers here:
ObservedObject view-model is still in memory after the view is dismissed
(2 answers)
Closed 2 years ago.
I have a List with several items, that open a DetailView which in turn holds a viewmodel. The viewmodel is supposed to have a service class that gets initialized when the detail view appears and should be deinitialized when navigating back.
However, the first problem is that in my example below, all 3 ViewModel instances are created at the same time (when ContentView is displayed) and never get released from memory (deinit is never called).
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: DetailView()) {
Text("Link")
}
NavigationLink(destination: DetailView()) {
Text("Link")
}
NavigationLink(destination: DetailView()) {
Text("Link")
}
}
}
}
}
struct DetailView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
Text("Hello \(viewModel.name)")
}
}
class ViewModel: ObservableObject {
#Published var name = "John"
private let heavyClient = someHeavyService()
init() { print("INIT VM") }
deinit { print("DEINIT VM") }
}
This is probably just how SwiftUI works, but I have a hard time thinking of a way to handle class objects that are part of a detail view's state, but are not supposed to instantiate until the detail view actually appears. An example would be video conferencing app, with rooms, where the room client that establishes connections etc. should only get initialized when actually entering the room and deinitialize when leaving the room.
I'd appreciate any suggestions on how to mange this. should I initialize the heavyClient at onAppear or something similar?
The problem is that DetailView() is getting initialized as part of the navigation link. One possible solution could be the LazyView from this post.
Implemented like so:
struct LazyView<Content: View>: View {
let build: () -> Content
init(_ build: #autoclosure #escaping () -> Content) {
self.build = build
}
var body: Content {
build()
}
}
And then wrap the DetailView() in the LazyView():
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: LazyView(DetailView())) {
Text("Link")
}
NavigationLink(destination: LazyView(DetailView())) {
Text("Link")
}
NavigationLink(destination: LazyView(DetailView())) {
Text("Link")
}
}
}
}
}
The only issue with this workaround is that there seems to always be one instance of ViewModel sitting around, though it's a large improvement.

Why does modifying the label of a NavigationLink change which View is displayed in SwiftUI?

I have an #EnvironmentObject called word (of type Word) whose identifier property I'm using for the label of a NavigationLink in SwiftUI. For the DetailView that is linked to the NavigationLink, all I have put is this:
struct DetailView: View {
#EnvironmentObject var word: Word
var body: some View {
VStack {
Text(word.identifier)
Button(action: {
self.word.identifier += "a"
}) {
Text("Click to add an 'a' to Word's identifier")
}
}
}
}
The ContentView that leads to this DetailView looks like this (I've simplified my actual code to isolate the problem).
struct ContentView: View {
#EnvironmentObject var word: Word
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Text(word.identifier)
}
}
}
}
When I tap the button on the DetailView, I'd expect it to update the DetailView with a new word.identifier that has an extra "a" appended onto it. When I tap it, however, it takes me back to the ContentView, albeit with an updated word.identifier. I can't seem to find a way to stay on my DetailView when the word.identifier being used by the ContentView's NavigationLink is modified. Also, I am running Xcode 11.3.1 and am currently unable to update, so if this is has been patched, please let me know.
Here is workaround solution
struct DetailView: View {
#EnvironmentObject var word: Word
#State private var identifier: String = ""
var body: some View {
VStack {
Text(self.identifier)
Button(action: {
self.identifier += "a"
}) {
Text("Click to add an 'a' to Word's identifier")
}
}
.onAppear {
self.identifier = self.word.identifier
}
.onDisappear {
self.word.identifier = self.identifier
}
}
}
This works as expected on iOS 13.4, assuming Word is something like:
class Word : ObservableObject {
#Published var identifier = "foo"
}