How to Access Content View's Elements Later in Swiftui

How to access content view's elements later in SwiftUI?

You should probably rethink your approach. What exactly is it you want to happen? You have some external data model type that fetches (or updates) data and you want your views to react to that? If that's the case, create an ObservableObject and pass that to your CustomClass.

struct CustomClass: View {

@ObservedObject var model: Model

var body: some View {
// base your view on your observed-object
}
}

Perhaps you want to be notified of events that originate from CustomClass?

struct CustomClass: View {

var onButtonPress: () -> Void = { }

var body: some View {
Button("Press me") { self.onButtonPress() }
}
}

struct ParentView: View {

var body: some View {
CustomClass(onButtonPress: { /* react to the press here */ })
}
}

Lastly, if you truly want some kind of tag on your views, you can leverage the Preferences system in SwiftUI. This is a more complicated topic so I will just point out what I have found to be a great resource here:
https://swiftui-lab.com/communicating-with-the-view-tree-part-1/

How to access to the children views in SwiftUI?

This is what you're looking for, if I'm understanding your question correctly:

struct FullButton<Label>: View where Label: View {
var action: () -> Void
var label: () -> Label

var body: some View {
Button(action: self.action, label: self.label)
}
}

This would allow you to pass whatever content you want to be displayed on your button, meaning that the code you have here would now work:

FullButton(action: {
print("touched")
}) {
Text("Button")
}

Update

After looking over your question several times, I've realized that your confusion is stemming from a misunderstanding of what is happening when you create a normal Button.

In the code below, I'm creating a Button. The button takes two arguments - action and label.

Button(action: {}, label: {
Text("Button")
})

If we look at the documentation for Button, we see that it is declared like this:

struct Button<Label> where Label : View

If we then look at the initializers, we see this:

init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)

Both action and label expect closures. action expects a closure with a return type of Void, and label expects a @ViewBuilder closure with a return type of Label. As defined in the declaration for Button, Label is a generic representing a View, so really, label is expecting a closure that returns a View.

This is not unique to Button. Take HStack, for example:

struct HStack<Content> where Content : View

init(alignment: VerticalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)

Content serves the same purpose here that Label does in Button.

Something else to note - when we create a button like this...

Button(action: {}) {
Text("Button")
}

...we're actually doing the same thing as this:

Button(action: {}, label: {
Text("Button")
})

In Swift, when the last argument in a method call is a closure, we can omit the argument label and append the closure to the outside of the closing parenthesis.

In SwiftUI, you cannot implicitly pass content to any View. The View must explicitly accept a @ViewBuilder closure in its initializer.

And so, you cannot pass a @ViewBuilder closure to FullButton unless FullButton accepts a @ViewBuilder closure as an argument in its initializer, as shown at the beginning of my answer.

Ways to access Model's variables in SwiftUI?

Here is some code that may help you.

import Foundation
import SwiftUI

struct ContentView: View {
var body: some View {
TestView()
}
}

struct CalmDocc: Identifiable, Codable, Hashable {
var content: String
var id: String
var calmwordcount: Int
}

class CalmManager: ObservableObject {
// for testing
@Published var calmDoccs: [CalmDocc] = [
CalmDocc(content: "content-1", id: "id-1", calmwordcount: 1),
CalmDocc(content: "content-2", id: "id-2", calmwordcount: 2),
CalmDocc(content: "content-3", id: "id-3", calmwordcount: 3)
]
@Published var calmContent: String = ""
@Published var idcalm: String = ""
}

struct TestView: View {
@StateObject var manager = CalmManager()

var body: some View {
List {
ForEach(manager.calmDoccs) { calmDocc in
VStack {
Text(calmDocc.id)
Text(calmDocc.content)
Text("count: \(calmDocc.calmwordcount)")
}
}
}
}
}

You should read again the basics at: https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html and in particular, how to use an array, and collections in general: (https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html). I recommend you also do the tutorial at: https://developer.apple.com/tutorials/swiftui/

EDIT-1: to show only a specific element of calmDoccs array:

struct TestView: View {
@StateObject var manager = CalmManager()

var body: some View {
if manager.calmDoccs.count > 0 {
Text(manager.calmDoccs[0].id)
Text(manager.calmDoccs[0].content)
Text("count: \(manager.calmDoccs[0].calmwordcount)")
}
}
}

EDIT-2: to get totalCount displayed.

struct TestView: View {
@StateObject var manager1 = CalmManager()
@StateObject var manager2 = CalmManager()

var body: some View {
if manager1.calmDoccs.count > 0 && manager2.calmDoccs.count > 0 {
let totalCount = manager1.calmDoccs[0].calmwordcount + manager2.calmDoccs[0].calmwordcount
Text("total count: \(totalCount)")

// yes, you can also do this, pay attention to the details
Text("total count: \(manager1.calmDoccs[0].calmwordcount + manager2.calmDoccs[0].calmwordcount)")

}
}
}

can I get the position of a `View` after layout in SwiftUI?

Use a GeometryReader to get the frame of each view and use the frame to determine the points for a path between the two views.

struct ContentView : View {
var body: some View {
GeometryReader { geometry -> Text in
let frame = geometry.frame(in: CoordinateSpace.local)
return Text("\(frame.origin.x), \(frame.origin.y), \(frame.size.width), \(frame.size.height)")
}
}
}

Inserting non-list elements in a SwiftUI view

i'll let you work out the padding and the rounding.

    struct ContentView: View {
private var color = Color(red: 32/255, green: 35/255, blue: 0/255)

var body: some View {
NavigationView {
Form {
Section(content: {
HStack {
Text("AA")
.foregroundColor(color)

}
HStack {
Text("B")
.foregroundColor(color)

}
}, footer: {
Image("fallLeaves")
})
}.navigationBarTitle(Text("Page"))
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Swiftui items get duplicated in all views when added to a single custom view

There is the anwser for your comment about add item in each CollapsedView2.
Because ListViewModel is not ObservableObject (ListViewModel is difference from each CollapsableItem). You should use "@State var items: [ItemModel]".

struct CollapsedView2<Content: View>: View {
@State var collapsableItem: CollapsableItem
// @State var listViewModel = ListViewModel()
@State var collapsed: Bool = true
@State var label: () -> Text
@State var content: () -> Content
@State private var show = true
@State var items: [ItemModel] = []
@State var count = 1

var body: some View {
VStack {
HStack{
Text("Hello")
.font(.title2.bold())

Spacer()

Button( action: { self.collapsed.toggle() },
label: {
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
)
.buttonStyle(PlainButtonStyle())

Button(action: saveButtonPressed, label: {
Image(systemName: "plus")
.font(.title2)
// .foregroundColor(.white)
})
}
VStack {
self.content()
}

ForEach(items) { item in
ListRowView (item: item)
}
.lineLimit(1)
.fixedSize(horizontal: true, vertical: true)
.frame(minHeight: 0, maxHeight: collapsed ? 0 : .none)
.animation(.easeInOut(duration: 1.0), value: show)
.clipped()
.transition(.slide)
}
}

func saveButtonPressed() {
addItem(title: "Hello \(count)")
count += 1
}

func addItem(title: String) {
let newItem = ItemModel(title: title)
items.append(newItem)
}

func updateItem(item: ItemModel) {

if let index = items.firstIndex(where: { $0.id == item.id}) {
items[index] = item.updateCompletion()
}
}
}

SwiftUI - Are views kept alive while navigating?

Does this mean the view is alive until it gets initialized again?

Yes, the view may be alive even after you navigate back to the parent view.


To better understand what's happening run the same code on the iPad simulator (preferably in the horizontal mode). You'll notice that the NavigationView is split in two parts: master and detail - this way you can see both parent and child view at once.

Now, if you perform the same experiment from your question, you'll see the child view remains present even if you navigate back. The same happens on iOS.

One way to prevent this can be to check if indices are present in the array:

struct NewView: View {
@ObservedObject var new_view_settings: Settings
@State var index = -1

var body: some View {
VStack {
Button(action: {
//self.index = self.new_view_settings.count - 1
}) {
Text("change index")
}

// check if `index` is in array
if self.index > -1 && self.index < self.new_view_settings.data.count {
Text("\(self.new_view_settings.data[index])")
}
}
}
}

Note: in general I don't recommend dealing with indices in SwiftUI views - there usually is a better way to pass data. Dealing with indices is risky.



Related Topics



Leave a reply



Submit