How to set variables in a class in a structure that also uses variables from the class Swift - swift

I'm trying to pull the variables from my class, SolutionAggregationViewController, and use them in my structure NewRatingView in place of the current state variables, currentRating, usualRating, and totalRatings. I also have a variable in SolutionAggregationViewController, userCurrentRating, which I want to write to Firebase using the structure of interactive 5 stars I've created in NewRatingView. I'm wondering how I need to have these two items communicate in that way.
struct NewRatingView: View {
var currentRatingTest = SolutionAggregationViewController()
var schools = ["North Avenue", "West Village", "Brittain"]
#State private var location = "West Village"
#State private var userCurrentRating: CGFloat = 0.0
#State private var userUsualRating: CGFloat = 0.0
#State private var isUserRatingCurrent = true
#State private var currentRating: CGFloat = 0
#State private var isUserRatingUsual = true
#State private var usualRating: CGFloat = 0
#State private var totalRatings: CGFloat = 1
var body: some View {
NavigationView{
ScrollView {
VStack(spacing: 16) {
Divider()
Text("Choose a dining location:")
.bold()
Picker("Choose a dining location:", selection: $location) {
ForEach(schools, id: \.self) {
Text($0)
}
}
Text("Current Rating")
.bold()
if self.isUserRatingCurrent {
VStack {
RatingsViewCurrent(onDidRateCurrent: self.userDidRateCurrent, ratingCurrent: self.$userCurrentRating)
.frame(width: .infinity, height: 100, alignment: .center)
}
} else {
VStack {
RatingsViewCurrent(ratingCurrent: self.$currentRating)
.frame(width: .infinity, height: 100, alignment: .center)
if userCurrentRating == 0.0 {
Button("Rate") {
self.isUserRatingCurrent = false
}
}
}
}
Text("Usual Rating")
.bold()
if self.isUserRatingUsual {
VStack {
RatingsViewUsual(onDidRateUsual: self.userDidRateUsual, ratingUsual: self.$userUsualRating)
.frame(width: .infinity, height: 100, alignment: .center)
Button("Submit Rating!") {
self.isUserRatingUsual = true
}
}
} else {
VStack {
RatingsViewUsual(ratingUsual: self.$usualRating)
.frame(width: .infinity, height: 100, alignment: .center)
if userUsualRating == 0.0 {
Button("Rate") {
self.isUserRatingUsual = false
}
}
}
}
}
}.navigationTitle("Add a Rating")
}
}
private func userDidRateCurrent(_ ratingCurrent: Int) {
// DispatchQueue.main.asyncAfter(deadline: .now() + 1.0, execute: { self.isUserRatingCurrent = false })
self.storeRatingInformation()
}
private func userDidRateUsual(_ ratingUsual: Int) {
self.storeRatingInformation()
}
private func storeRatingInformation() {
let id = location
print("StoredInformation")
let ratingData = ["location": location, "currentRating": currentRating, "usualRating": usualRating, "totalRatings": totalRatings] as [String : Any]
let db = Firestore.firestore()
db.collection("RatingInformation").document(id)
.setData(ratingData) { error in
if let error = error {
print(error)
return
}
}
print("RatingData: \(ratingData)")
}
}
class SolutionAggregationViewController: UIViewController {
var db: Firestore!
struct NewRating {
let id: String
let location: String
let currentRating: Float
let usualRating: Float
let totalRatings: Float
init(id: String, location: String, currentRating: Float, usualRating: Float, totalRatings: Float) {
self.id = id
self.location = location
self.currentRating = currentRating
self.usualRating = usualRating
self.totalRatings = totalRatings
}
}
let NewRating = NewRating(id: "West Village", location: "West Village", currentRating: 2.0, usualRating: 2.0, totalRatings: 4.0)
// [END restaurant_struct]
struct Rating {
let userCurrentRating: Float
}
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
}
func addRatingTransaction(restaurantRef: DocumentReference, userCurrentRating: Float) {
let ratingRef: DocumentReference = restaurantRef.collection("RatingInformation").document()
db.runTransaction({ (transaction, errorPointer) -> Any? in
do {
let restaurantDocument = try transaction.getDocument(restaurantRef).data()
guard var restaurantData = restaurantDocument else { return nil }
// Compute new number of ratings
let numRatings = restaurantData["totalRatings"] as! Float
let newNumRatings = numRatings + 1
// Compute new average rating
let avgRating = restaurantData["currentRating"] as! Float
let oldRatingTotal = avgRating * Float(numRatings)
let newAvgRating = (oldRatingTotal + userCurrentRating) / Float(newNumRatings)
// Set new restaurant info
restaurantData["totalRatings"] = newNumRatings
restaurantData["currentRating"] = newAvgRating
// Commit to Firestore
transaction.setData(restaurantData, forDocument: restaurantRef)
transaction.setData(["userCurrentRating": userCurrentRating], forDocument: ratingRef)
} catch {
}
return nil
}) { (object, err) in
}
}
}

Related

Read Data from Firestore Subcollection and Aggregate to Value in Firestore Collection Swift

I have a reviews app where the numerical ratings are collected for each location in their own subcollection in Firestore. I have a value under each collection document that has the average rating and total number of ratings for that location. I am trying to figure out how to read the data from my subcollection and then manipulate it to find the averages, and then write it back to my collection.
This is the code which I currently have and I'm able to write my reviews to the database, and I'm not getting any errors, but my function addRatingTransaction isn't doing anything when I run the app.
struct NewRatingView: View {
let db = Firestore.firestore()
#ObservedObject var model = DataManagerRating()
#State private var newRating = 0
var schools = ["North Avenue", "West Village", "Brittain"]
var ratings = [1.0, 2.0, 3.0, 4.0, 5.0]
#State var userCurrentRating: CGFloat = 0.0
#State private var isUserRatingCurrent = true
#State private var currentRating: CGFloat = 0.0
#State var userUsualRating: CGFloat = 0.0
#State private var isUserRatingUsual = true
#State private var usualRating: CGFloat = 0.0
#State private var userLocation = "West Village"
var body: some View {
VStack(spacing: 16) {
Text("Choose a dining location:")
.bold()
Picker("Choose a dining location:", selection: $userLocation) {
ForEach(schools, id: \.self) {
Text($0)
}
}
Text("Current Rating").bold()
if self.isUserRatingCurrent {
VStack {
RatingsViewCurrent(onDidRateCurrent: self.userDidRateCurrent, ratingCurrent: self.$userCurrentRating)
.frame(width: .infinity, height: 100, alignment: .center)
}
} else {
VStack {
RatingsViewCurrent(ratingCurrent: self.$currentRating)
.frame(width: .infinity, height: 100, alignment: .center)
if userCurrentRating == 0.0 {
Button("Rate") {
self.isUserRatingCurrent = false
}
}
}
}
Text("Usual Rating").bold()
if self.isUserRatingUsual {
VStack {
RatingsViewUsual(onDidRateUsual: self.userDidRateUsual, ratingUsual: self.$userUsualRating)
.frame(width: .infinity, height: 100, alignment: .center)
}
} else {
VStack {
RatingsViewUsual(ratingUsual: self.$usualRating)
.frame(width: .infinity, height: 100, alignment: .center)
if userUsualRating == 0.0 {
Button("Rate") {
self.isUserRatingUsual = false
}
}
}
}
Button(action: {
model.addData(userLocation: userLocation, userCurrentRating: Float(userCurrentRating), userUsualRating: Float(userUsualRating))
print("added user rating")
addRatingTransaction(restaurantRef: db.collection("RatingInformation").document("Brittain").collection("Ratings").document(), rating: Float(userCurrentRating))
print("added transaction")
}, label: {
Text("Submit")
})
}
.padding()
}
func userDidRateCurrent(_ ratingCurrent: Int) { print("ratingcurrent \(ratingCurrent)")
}
func userDidRateUsual(_ ratingUsual: Int) {
print("ratingusual \(ratingUsual)")
}
func getRatingsSubcollection() {
// [START get_ratings_subcollection]
db.collection("RatingInformation")
.document("Brittain")
.collection("Ratings")
.getDocuments() { (querySnapshot, err) in
// ...
}
// [END get_ratings_subcollection]
}
// [START add_rating_transaction]
func addRatingTransaction(restaurantRef: DocumentReference, rating: Float) {
let ratingRef: DocumentReference = restaurantRef.collection("Ratings").document()
let rating = currentRating
db.runTransaction({ (transaction, errorPointer) -> Any? in
do {
let restaurantDocument = try transaction.getDocument(restaurantRef).data()
guard var restaurantData = restaurantDocument else { return nil }
// Compute new number of ratings
let totalRatings = restaurantData["totalRatings"] as! Int
let newNumRatings = totalRatings + 1
print("New Num Ratings")
print(newNumRatings)
// Compute new average rating
let currentRating = restaurantData["currentRating"] as! Float
let oldRatingTotal = currentRating * Float(totalRatings)
let newAvgRating = (oldRatingTotal + Float(rating)) / Float(newNumRatings)
print("New Average Rating")
print(newAvgRating)
// Set new restaurant info
// restaurantData["totalRatings"] = newNumRatings
// restaurantData["currentRating"] = newAvgRating
// Commit to Firestore
transaction.setData(restaurantData, forDocument: restaurantRef)
transaction.setData(["Ratings": rating], forDocument: ratingRef)
print(newAvgRating)
print(newNumRatings)
} catch {
// Error getting restaurant data
// ...
}
return nil
}) { (object, err) in
// ...
}
}
}

How to dynamically change GridItems in LazyVGrid with MagnificationGesture [Zoom In, Out] in SwiftUI?

The idea is to recreate the same photo layout behaviour like in Apple Photo Library when I can zoom in and out with 1, 3 or 5 photos in a row. I'm stack in a half way. For that I use a MagnificationGesture() and based on gesture value I update number of GridItems() in LazyVGrid().
Please let me know how to achieve it. Thanks a lot ๐Ÿ™
Here's code:
import SwiftUI
struct ContentView: View {
let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
#State private var colums = Array(repeating: GridItem(), count: 1)
// #GestureState var magnifyBy: CGFloat = 1.0
#State var magnifyBy: CGFloat = 1.0
#State var lastMagnifyBy: CGFloat = 1.0
let minMagnifyBy = 1.0
let maxMagnifyBy = 5.0
var magnification: some Gesture {
MagnificationGesture()
// .updating($magnifyBy) { (currentState, pastState, trans) in
// pastState = currentState.magnitude
// }
.onChanged { state in
adjustMagnification(from: state)
print("Current State \(state)")
}
.onEnded { state in
adjustMagnification(from: state)
// withAnimation(.spring()) {
// validateMagnificationLimits()
// }
lastMagnifyBy = 1.0
}
}
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: colums) {
ForEach(1..<101) { number in
colors[number % colors.count]
.overlay(Text("\(number)").font(.title2.bold()).foregroundColor(.white))
.frame(height: 100)
}
}
.scaleEffect(magnifyBy)
.gesture(magnification)
.navigationTitle("๐Ÿงจ Grid")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring(response: 0.8)) {
colums = Array(repeating: .init(), count: colums.count == 5 ? 1 : colums.count % 5 + 2)
}
} label: {
Image(systemName: "square.grid.3x3")
.font(.title2)
.foregroundColor(.primary)
}
}
}
}
}
}
private func adjustMagnification(from state: MagnificationGesture.Value) {
let stepCount = Int(min(max(1, state), 5))
// let delta = state / lastMagnifyBy
// magnifyBy *= delta
withAnimation(.linear) {
colums = Array(repeating: GridItem(), count: stepCount)
}
lastMagnifyBy = state
}
private func getMinMagnificationAllowed() -> CGFloat {
max(magnifyBy, minMagnifyBy)
}
private func getMaxMagnificationAllowed() -> CGFloat {
min(magnifyBy, maxMagnifyBy)
}
private func validateMagnificationLimits() {
magnifyBy = getMinMagnificationAllowed()
magnifyBy = getMaxMagnificationAllowed()
}
}
Here you go. This uses a TrackableScrollView (git link in the code).
I implemented an array of possible zoomStages (cols per row), to make switching between them easier.
Next to dos would be scrolling back to the magnification center, so the same item stays in focus. And maybe an opacity transition in stead of rearranging the Grid. Have fun ;)
import SwiftUI
// https://github.com/maxnatchanon/trackable-scroll-view.git
import SwiftUITrackableScrollView
struct ContentView: View {
let colors: [Color] = [.red, .purple, .yellow, .green, .blue, .mint, .orange]
let zoomStages = [1, 3, 5, 9, 15]
#State private var zoomStageIndex = 0
var colums: [GridItem] { Array(repeating: GridItem(spacing: 0), count: zoomStages[zoomStageIndex]) }
#State var magnifyBy: CGFloat = 1.0
#State private var scrollViewOffset = CGFloat.zero // SwiftUITrackableScrollView: Content offset available to use
var body: some View {
NavigationView {
TrackableScrollView(.vertical, showIndicators: false, contentOffset: $scrollViewOffset) {
LazyVGrid(columns: colums, spacing: 0) {
ForEach(0..<500) { number in
colors[number % colors.count]
.overlay(
Text("\(number)").font(.title2.bold()).foregroundColor(.white)
.minimumScaleFactor(0.1)
)
.aspectRatio(1, contentMode: .fit) // always squares
.id(number)
}
}
.scaleEffect(magnifyBy, anchor: .top)
// offset to correct magnify "center" point
.offset(x: 0, y: (scrollViewOffset + UIScreen.main.bounds.midY) * (1 - magnifyBy) )
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
withAnimation(.spring(response: 0.8)) {
if zoomStageIndex < zoomStages.count-1 {
zoomStageIndex += 1
} else {
zoomStageIndex = 0
}
}
} label: {
Image(systemName: "square.grid.3x3")
.font(.title2)
.foregroundColor(.primary)
}
}
}
.gesture(magnification)
}
.ignoresSafeArea()
}
}
var magnification: some Gesture {
MagnificationGesture()
.onChanged { state in
magnifyBy = state
}
.onEnded { state in
// find predefined zoom(index) that is closest to actual pinch zoom value
let newZoom = Double(zoomStages[zoomStageIndex]) * 1 / state
let newZoomIndex = findClosestZoomIndex(value: newZoom)
// print("***", zoomStages[zoomStageIndex], state, newZoom, newZoomIndex)
withAnimation(.spring(response: 0.8)) {
magnifyBy = 1 // reset scaleEffect
zoomStageIndex = newZoomIndex // set new zoom level
}
}
}
func findClosestZoomIndex(value: Double) -> Int {
let distanceArray = zoomStages.map { abs(Double($0) - value) } // absolute difference between zoom stages and actual pinch zoom
// print("dist:", distanceArray)
return distanceArray.indices.min(by: {distanceArray[$0] < distanceArray[$1]}) ?? 0 // return index of element that is "closest"
}
}

SwiftUI - Fatal error: index out of range on deleting element from an array

I have an array on appstorage. I'm displaying its elements with foreach method. It has swipe to delete on each element of the array. But when i delete one, app is crashing. Here is my first view;
struct View1: View {
#Binding var storedElements: [myElements]
var body: some View{
GeometryReader {
geometry in
VStack{
ForEach(storedElements.indices, id: \.self){i in
View2(storedElements: $storedElements[i], pic: $storedWElements[i].pic, allElements: $storedElements, index: i)
}
}.frame(width: geometry.size.width, height: geometry.size.height / 2, alignment: .top).padding(.top, 25)
}
}
}
And View 2;
struct View2: View {
#Binding var storedElements: myElements
#Binding var pic: String
#Binding var allElements: [myElements]
var index: Int
#State var offset: CGFloat = 0.0
#State var isSwiped: Bool = false
#AppStorage("pics", store: UserDefaults(suiteName: "group.com.some.id"))
var arrayData: Data = Data()
var body : some View {
ZStack{
Color.red
HStack{
Spacer()
Button(action: {
withAnimation(.easeIn){
delete()}}) {
Image(systemName: "trash").font(.title).foregroundColor(.white).frame(width: 90, height: 50)
}
}
View3(storedElements: $storedElements).background(Color.red).contentShape(Rectangle()).offset(x: self.offset).gesture(DragGesture().onChanged(onChanged(value:)).onEnded(onEnd(value:)))
}.frame(width: 300, height: 175).cornerRadius(30)
}
}
func onChanged(value: DragGesture.Value) {
if value.translation.width < 0 {
if self.isSwiped {
self.offset = value.translation.width - 90
}else {
self.offset = value.translation.width
}
}
}
func onEnd(value: DragGesture.Value) {
withAnimation(.easeOut) {
if value.translation.width < 0 {
if -value.translation.width > UIScreen.main.bounds.width / 2 {
self.offset = -100
delete()
}else if -self.offset > 50 {
self.isSwiped = true
self.offset = -90
}else {
self.isSwiped = false
self.offset = 0
}
}else {
self.isSwiped = false
self.offset = 0
}
}
}
func delete() {
//self.allElements.remove(at: self.index)
if let index = self.allElements.firstIndex(of: storedElements) {
self.allElements.remove(at: index)
}
}
}
OnChange and onEnd functions are for swiping. I think its the foreach method that is causing crash. Also tried the commented line on delete function but no help.
And I know its a long code for a question. I'm trying for days and tried every answer here but none of them solved my problem here.
In your myElements class/struct, ensure it has a unique property. If not add one and upon init set a unique ID
public class myElements {
var uuid: String
init() {
self.uuid = NSUUID().uuidString
}
}
Then when deleting the element, instead of
if let index = self.allElements.firstIndex(of: storedElements) {
self.allElements.remove(at: index)
}
Use
self.allElements.removeAll(where: { a in a.uuid == storedElements.uuid })
This is array bound safe as it does not use an index

State variable not update in SwiftUI

I have struct DBScrollViewCellWrapper. that display contain
struct DBScrollViewCellWrapper: View, Identifiable, Equatable {
let id = UUID().uuidString
let view: AnyView
#State var showSelectionLine: Bool = false
var body: some View {
VStack(spacing: 0){
view
if self.showSelectionLine{
Rectangle()
.frame(width: 10, height: 1)
.foregroundColor(.red)
}
}
}
static func == (lhs: DBScrollViewCellWrapper, rhs: DBScrollViewCellWrapper) -> Bool { lhs.id == rhs.id }
}
then generate number of DBScrollViewCellWrapper cell. when tap on cell, display tapped cell selected with line.
struct DBScrollView: View {
let views: [DBScrollViewCellWrapper]
var showsIndicators = false
var completion:(DBScrollViewCellWrapper,Int)->Void = {x,index in}
var isHorizontal: Bool = false
var leadingSpacing: CGFloat = 0
var trailingSpacing: CGFloat = 0
var itemSpacing: CGFloat = 5
var isFixSize: Bool = false
var fixWidth: CGFloat = .infinity
var fixHeight: CGFloat = .infinity
#State var showSelectionLine: Bool = false
#State private var previousItem : DBScrollViewCellWrapper?
init(views: [DBScrollViewCellWrapper],
showsIndicators: Bool = false,
isHorizontal: Bool = false,
leadingSpacing: CGFloat = 0,
trailingSpacing: CGFloat = 0,
itemSpacing: CGFloat = 5,
isFixSize: Bool = false,
fixWidth: CGFloat = .infinity,
fixHeight: CGFloat = .infinity,
completion: #escaping (DBScrollViewCellWrapper,Int)->Void = {val,index in}) {
self.views = views.map { $0 } //DBScrollViewCellWrapper(view: $0)
self.showsIndicators = showsIndicators
self.completion = completion
self.isHorizontal = isHorizontal
self.leadingSpacing = leadingSpacing
self.trailingSpacing = trailingSpacing
self.itemSpacing = itemSpacing
self.isFixSize = isFixSize
self.fixWidth = fixWidth
self.fixHeight = fixHeight
}
var body: some View {
GeometryReader(content: { geometry in
ScrollView(isHorizontal ? .horizontal : .vertical, showsIndicators: showsIndicators, content: {
self.generateViews(in: geometry)
})
.padding(.leading, self.leadingSpacing)
.padding(.trailing, self.trailingSpacing)
})
}
private func generateViews(in geometry: GeometryProxy) -> some View{
return ZStack{
if isHorizontal{
HStack(spacing: itemSpacing){
ForEach(self.views) { item in
item
.padding(5)
.border(Color.black)
.onTapGesture(count: 1, perform: {
self.tapped(value: item)
})
}
Spacer()
}
}else{
VStack(spacing: itemSpacing){
ForEach(self.views, id: \.id) { item in
item
.padding(5)
.border(Color.clear)
.onTapGesture(count: 1, perform: {
self.tapped(value: item)
})
}
Spacer()
}
}
}
}
func tapped(value: DBScrollViewCellWrapper) {
guard let index = views.firstIndex(of: value) else { assert(false, "This should never happen"); return }
value.showSelectionLine = true
completion(value,index)
}
}
Preview Code:
struct DBScrollView_Previews: PreviewProvider {
static var previews: some View {
let arr = Array(0...100)
let arrView = arr.map{DBScrollViewCellWrapper(view: AnyView(Text("\($0)")))}
DBScrollView(views: arrView, isHorizontal: false) { (cell, inx) in
cell.showSelectionLine = true
}
}
}
Problem
when tapped on cell, changed the value of cell but that not update.
Redesigned code for selection
struct DBScrollView: View {
private let views: [DBScrollViewCellWrapper]
var showsIndicators = false
var completion:(DBScrollViewCellWrapper,Int)->Void = {x,index in}
var isHorizontal: Bool = false
var leadingSpacing: CGFloat = 0
var trailingSpacing: CGFloat = 0
var itemSpacing: CGFloat = 5
var isFixSize: Bool = false
var fixWidth: CGFloat = .infinity
var fixHeight: CGFloat = .infinity
#State var selectedIndex: Int = -1
#State var showSelectionLine: Bool = false
#State private var previousItem : DBScrollViewCellWrapper?
init(views: [AnyView],
showsIndicators: Bool = false,
isHorizontal: Bool = false,
leadingSpacing: CGFloat = 0,
trailingSpacing: CGFloat = 0,
itemSpacing: CGFloat = 5,
isFixSize: Bool = false,
fixWidth: CGFloat = .infinity,
fixHeight: CGFloat = .infinity,
completion: #escaping (DBScrollViewCellWrapper,Int)->Void = {val,index in}) {
self.views = views.map { DBScrollViewCellWrapper(view: AnyView($0))}
self.showsIndicators = showsIndicators
self.completion = completion
self.isHorizontal = isHorizontal
self.leadingSpacing = leadingSpacing
self.trailingSpacing = trailingSpacing
self.itemSpacing = itemSpacing
self.isFixSize = isFixSize
self.fixWidth = fixWidth
self.fixHeight = fixHeight
}
var body: some View {
GeometryReader(content: { geometry in
ScrollView(isHorizontal ? .horizontal : .vertical, showsIndicators: showsIndicators, content: {
self.generateViews(in: geometry)
})
.padding(.leading, self.leadingSpacing)
.padding(.trailing, self.trailingSpacing)
})
}
private func generateViews(in geometry: GeometryProxy) -> some View{
return ZStack{
if isHorizontal{
HStack(alignment: .center, spacing: itemSpacing){
ForEach(self.views.indices, id:\.self) { index in
let item = self.views[index]
VStack(spacing: 0){
item.view
.foregroundColor(self.selectedIndex == index ? Color.yellow : Color.white)
.padding(5)
.onTapGesture(count: 1, perform: {
self.selectedIndex = index
self.tapped(value: item)
})
Rectangle()
.frame(width: self.selectedIndex == index ? 10 : 0, height: 1, alignment: .center)
.foregroundColor(Color.yellow)
}
}
Spacer()
}
}else{
VStack(spacing: itemSpacing){
ForEach(self.views.indices, id: \.self) { index in
let item = self.views[index]
VStack(spacing: 0){
item.view
.padding(5)
.border(Color.white)
.onTapGesture(count: 1, perform: {
self.selectedIndex = index
self.tapped(value: item)
})
Rectangle()
.frame(width: self.selectedIndex == index ? 10 : 0, height: 1, alignment: .center)
.foregroundColor(Color.yellow)
}
}
Spacer()
}
}
}
}
func tapped(value: DBScrollViewCellWrapper) {
guard let index = views.firstIndex(of: value) else { assert(false, "This should never happen"); return }
completion(value,index)
}
}
struct DBScrollViewCellWrapper: Identifiable, Equatable {
let id = UUID().uuidString
let view: AnyView
static func == (lhs: DBScrollViewCellWrapper, rhs: DBScrollViewCellWrapper) -> Bool { lhs.id == rhs.id } } struct DBScrollView_Previews: PreviewProvider {
static var previews: some View {
let arr = Array(0...100)
let arrView = arr.map{AnyView(Text("\($0)"))}
DBScrollView(views: arrView, isHorizontal: true) { (cell, inx) in
}
.background(Color.black)
}
}

How do you fix "Cannot use mutating member on immutable value: 'self' is immutable"?

How can I fix the error Cannot use mutating member on immutable value: 'self' is immutable on the line self.prepareConfetti(screenWidth: CGFloat(geo.size.width))?
There are a number of StackOverflow questions on this subject, but none helped me.
My code:
struct ContentView: View {
#State private var confettiTimer = Timer.publish(every: 0.0166, on: .main, in: .common).autoconnect()
private var confettiX = [CGFloat]()
private var confettiY = [CGFloat]()
private var confettiWidth = [CGFloat]()
private var confettiHeight = [CGFloat]()
private let numOfConfetti: Int = 80
var body: some View {
GeometryReader { geo in
ForEach(0..<self.confettiY.count, id: \.self) { i in
Path { path in
path.addRect(
CGRect(
x: self.confettiX[i],
y: self.confettiY[i],
width: self.confettiWidth[i],
height: self.confettiHeight[i]
)
)
}
.fill(Color.black)
.onAppear {
self.prepareConfetti(screenWidth: CGFloat(geo.size.width)) //This gives the error
}
.onReceive(self.confettiTimer) { time in
}
}
}
}
mutating func prepareConfetti(screenWidth: CGFloat) {
for _ in 0..<self.numOfConfetti {
let x: CGFloat = 0.0
let y = screenWidth * 0.5
self.confettiX.append(x)
self.confettiY.append(y)
self.confettiWidth.append(20.0)
self.confettiHeight.append(20.0)
}
}
}
You cannot mutate SwiftUI view in that way, you should use those properties as states
struct ContentView: View {
#State private var confettiTimer = Timer.publish(every: 0.0166, on: .main, in: .common).autoconnect()
#State private var confettiX = [CGFloat]()
#State private var confettiY = [CGFloat]()
#State private var confettiWidth = [CGFloat]()
#State private var confettiHeight = [CGFloat]()
private let numOfConfetti: Int = 80
var body: some View {
GeometryReader { geo in
ForEach(0..<self.confettiY.count, id: \.self) { i in
Path { path in
path.addRect(
CGRect(
x: self.confettiX[i],
y: self.confettiY[i],
width: self.confettiWidth[i],
height: self.confettiHeight[i]
)
)
}
.fill(Color.black)
.onAppear {
self.prepareConfetti(screenWidth: CGFloat(geo.size.width)) //This gives the error
}
.onReceive(self.confettiTimer) { time in
}
}
}
}
private func prepareConfetti(screenWidth: CGFloat) {
for _ in 0..<self.numOfConfetti {
let x: CGFloat = 0.0
let y = screenWidth * 0.5
self.confettiX.append(x)
self.confettiY.append(y)
self.confettiWidth.append(20.0)
self.confettiHeight.append(20.0)
}
}
}