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
Checking User Location Permission Status on iOS 14
Xcode Build Perfect Failure -- Copenssl Not Found
Xcframework Issue, a Library with the Identifier "Ios-Armv7_Arm64" Already Exists
How Would I Create a Constant That Could Be One of Several Strings Depending on Conditions
Getting Data Out of Nsdata with Swift
Overriding Generic Function Error in Swift
How to Use Case Enum Comparison as a Boolean Expression
Implemented Helper App But Does Not Launch on Login
Swiftui Overlay Blocking List Scroll Events
Drag and Drop Image from Uicollectionview to Uiview
Write into Settings Bundle in Swift
Making Sklabelnode as a Crop Node of Skshapenode
How to Use a Protocol with Optional Class Methods in an Extension with Generic in Swift