SwiftUI - How to create and use an array of custom types
You cannot perform any operation inside the ForEach. The ForEach expects to have a single View returned from it. But if you want to append data inside the ForEach then you can do that when the onAppear() closure is called on your View which is inside the ForEach.
This is what your code will look like when the elements array is appended in the onAppear of your View.
VStack {
ForEach (0..<26) { index in
Text("\(index) Hello, World!").onAppear() {
elements.append(Element(direction: "Direction \(index)", movement: "Movement \(index)", multiplier: index))
}
}
}
And you are getting the "immutable error" because you're probably declaring your elements variable inside the struct and properties inside the struct are immutable. So to be able to append new values you either need to annotate your elements variable with @State or you can declare it outside of the struct
Outside of struct declaration:
var elements: [Element] = []
struct MyView: View {
}
With annotation but inside struct declaration:
struct MyView: View {
@State var elements: [Element] = []
}
And you also need to know that changing values of @State annotated properties updates the UI but this shouldn't be something to worry about as SwiftUI calculates the changes in the UI and updates only the new changes
How to have a dynamic List of Views using SwiftUI
Looks like the answer was related to wrapping my view inside of AnyView
struct ContentView : View {
var myTypes: [Any] = [View1.self, View2.self]
var body: some View {
List {
ForEach(0..<myTypes.count) { index in
self.buildView(types: self.myTypes, index: index)
}
}
}
func buildView(types: [Any], index: Int) -> AnyView {
switch types[index].self {
case is View1.Type: return AnyView( View1() )
case is View2.Type: return AnyView( View2() )
default: return AnyView(EmptyView())
}
}
}
With this, i can now get view-data from a server and compose them. Also, they are only instanced when needed.
Switching between an array of views in swiftUI
View is just a struct. You can store it in an array or other attribute type and then use it in another Views. AnyView
is the good solution if you really want to mix different types of View in one collection.
To dynamically show that collection I like to use ForeEach. You can iterate not the exact Views but indices of an array. Therefore SwiftUI will make a key of this View from an array index of it. But it have a cost. You can't dynamically change the content of that array. it must be constant at all parent View lifetime.
To be honest, you actually can iterate the exact View through the ForeEach but you need to make them Identyfiable (they must have an public var id = UUID()
attribute).
import SwiftUI
let dotSize:CGFloat = 24
let diceSize:CGFloat = 128
let fieldSide:CGFloat = 32
struct ContentView: View {
var diceViews:[AnyView] = [AnyView(oneDotDice()), AnyView(twoDotDice())]
@State var showInt: Int = 1
var body: some View {
ZStack{
ForEach(diceViews.indices){ind in
ZStack{
if ind == self.showInt{
self.diceViews[ind]
.transition(.scale(scale: 0, anchor: .center))
.onTapGesture {
let newShow = Int.random(in: 0...1)
print("new show id \(newShow)")
withAnimation(.linear(duration: 0.3)){
self.showInt = newShow
}
}
}
}
}
}
}
}
struct oneDotDice: View {
var text: String = "1"
var body: some View {
Rectangle()
.fill(Color.clear)
.frame(width: diceSize, height: diceSize)
.border(Color.black)
.background(Text(text))
}
}
struct twoDotDice: View {
var text: String = "2"
var body: some View {
Rectangle()
.fill(Color.clear)
.frame(width: diceSize, height: diceSize)
.border(Color.black)
.background(Text(text))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
By the way, I change your random logic, not sure how it was working, but I think its not really important for your question.
Style views passed to custom view in SwiftUI
Like This :-
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
CustomText(text: "kjahsfgdi f")
Spacer()
CustomText(text: "abc")
Spacer()
CustomText(text: "abc")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CustomText : View{
@State var text: String
var body: some View {
Text(text)
.lineLimit(nil)
.frame(width: 100, height: nil, alignment: .center)
}
}
Create a List with an array of custom classes in SwiftUI
You are mixing two separate things. @State is meant to be used for anything internal to the view. While @BindableObject, is meant to hold something external to the view (i.e., your data model). When you define a @BindableObject, you do not reference it with @State, but with @ObjectBinding.
I don't know what was the case, but if you intend to use @State, the implementation should have been like this:
struct Media {
var id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
struct ContentView : View {
@State var media = [
Media(id: 0, name: "Name 0"),
Media(id: 1, name: "Name 1"),
Media(id: 2, name: "Name 2"),
Media(id: 3, name: "Name 3"),
]
var body: some View {
VStack {
List(media.identified(by: \.id)) { media in
Text(media.name)
}
HStack {
Button(action: {
self.media[0].name = "Name \(Int.random(in: 100...199))"
}) {
Text("Change")
}
Button(action: {
self.media.append(Media(id: 4, name: "Name 4"))
}) {
Text("Add")
}
}
}
}
}
You would mostly use @State for some internal state of the view, but also while prototyping. What was your intention with the Media object?
UPDATE
To implement it with a @ObjectBinding:
Supposing you are instantiating your CustomView in the SceneDelegate, you need to pass it the library:
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
let library = MediaLibrary(store: [Media(id: 0, name: "Name 0"),
Media(id: 1, name: "Name 1"),
Media(id: 2, name: "Name 2"),
Media(id: 3, name: "Name 3")])
window.rootViewController = UIHostingController(rootView: ContentView(library: library))
self.window = window
window.makeKeyAndVisible()
}
And the implementation:
struct Media {
let id: Int
var name: String
init(id: Int, name: String) {
self.id = id
self.name = name
}
}
class MediaLibrary: BindableObject {
typealias PublisherType = PassthroughSubject<Void, Never>
var didChange = PublisherType()
var store: [Media] {
didSet {
didChange.send()
}
}
init(store: [Media]) {
self.store = store
}
}
struct ContentView : View {
@ObjectBinding var library: MediaLibrary
var body: some View {
VStack {
List(self.library.store.identified(by: \.id)) { media in
Text(media.name)
}
HStack {
Button(action: {
self.library.store[0].name = "Name \(Int.random(in: 100...199))"
}) {
Text("Change")
}
Button(action: {
self.library.store.append(Media(id: 4, name: "Name 4"))
}) {
Text("Add")
}
}
}
}
}
Related Topics
How, Exactly, Do I Render Metal on a Background Thread
Way to Purge All But One Object Types (Models) in a Realm
Swift Firebase Storage How to Retrieve Image with Unknow Name(Nsuuid)
Fetch Coredata with One to Many Relationship in Swift
Simple Observable Struct with Rxswift
Delegate Must Respond to Locationmanager:Didupdatelocations Swift Eroor
Does Nsnumberformatter.Stringfromnumber Ever Return Nil
How Have Multiple Init() with Swift
Implementing Stringliteralconvertible on Nsurl
Crop/Mask Circular Image Node in Sprite Kit Gives Jagged Edges
Retrieving an Array from Firebase
Given a Function Parameter of Type [Int]; Can It Be Constrained to Not Be Empty
Difference Between Optional and Forced Unwrapping
Decode Dictionary with Random Initial Key
Why Do We Need to Set Delegate to Self? Why Isn't It Defaulted by the Compiler