UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: appending problem

Forums > SwiftUI

This application is made to visualize graph. The problem is that when we press add button we want to append node to matrix of connections. When we print size of matrix it is proper. When we want to display buttons of connections their number is static not increasing. When we added new node, number of elements in matrix has been changed, so we don't know where can be a problem.

import SwiftUI

struct ContentView: View {
    @StateObject var nodes = Nodes()
    @StateObject var matrix = Matrix()
    @State var showSidebar: Bool = false
    @State var id = 0

    var body: some View {

        SideBarStack(sidebarWidth: 200, showSidebar: $showSidebar) {
            NavigationView{
                VStack(){
                    Form {
                        Section {
                            TextField("\(nodes.n_array[id].text)", text: $nodes.n_array[id].text)
                            } header: { Text("Text") }

                        Section {
                                Picker("Red", selection: $nodes.n_array[id].red) {

                                    ForEach(0...255, id: \.self) { red in
                                        Text("\(red)")
                                    }

                                }

                                Picker("Green", selection: $nodes.n_array[id].green) {

                                    ForEach(0...255, id: \.self) { green in
                                        Text("\(green)")
                                    }

                                }

                                Picker("Blue", selection: $nodes.n_array[id].blue) {

                                    ForEach(0...255, id: \.self) { blue in
                                        Text("\(blue)")
                                    }

                                }

                        } header: { Text("Color") }

                        Section {

                            ForEach(0..<matrix.arr[id].count) { y in  // buttons of connections
                                if id != y {
                                    Button(action:
                                        {
                                        matrix.arr[id][y] = !matrix.arr[id][y] // on/off when buton pressed
                                        matrix.arr[y][id] = matrix.arr[id][y]
                                        }) {
                                            if matrix.arr[id][y] {
                                                Text("ON with \(y)")
                                                .fontWeight(.bold)
                                                .font(.body)
                                                .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10))
                                                .background(Color.green)
                                                .foregroundColor(.white)
                                                .cornerRadius(20)
                                            }
                                            else {
                                                Text("OFF with \(y)")
                                                .fontWeight(.bold)
                                                .font(.body)
                                                .padding(EdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10))
                                                .background(Color.red)
                                                .foregroundColor(.white)
                                                .cornerRadius(20)
                                            }
                                        }
                                }
                            }
                        }header: { Text("Connections") }

                    }

                    HStack(){

                        Button(action:
                            {

                            nodes.n_array.remove(at: id)

                            if id == nodes.n_array.count
                            {
                                id = id - 1

                                for x in (id+1)..<nodes.n_array.count
                                {

                                    nodes.n_array[x].id -= 1

                                }
                            }
                            else
                            {
                                for x in id..<nodes.n_array.count
                                {

                                    nodes.n_array[x].id -= 1

                                }
                            }

                        }) {

                            Text("Delete")
                                .fontWeight(.bold)
                                .font(.body)
                                .padding(EdgeInsets(top: 10, leading: 50, bottom: 10, trailing: 50))
                                .background(Color.red)
                                .foregroundColor(.white)
                                .cornerRadius(20)
                        }

                    }
                }
                }

                } content: {
                    ZStack{
                        ZStack {
                            ForEach(0..<matrix.arr.count) { x in  // draw edges 
                                ForEach(x+1..<matrix.arr[x].count) { y in
                                    if matrix.arr[x][y] {
                                        makeLine(from: nodes.n_array[x], to: nodes.n_array[y])
                                    }
                                }
                            }

                            ForEach($nodes.n_array) { $node in
                                node.drawShape()
                                    .gesture(DragGesture()
                                        .onChanged { value in
                                            node.offsetX = value.translation.width
                                            node.offsetY = value.translation.height
                                        }
                                        .onEnded { value in
                                            node.x += node.offsetX
                                            node.y += node.offsetY
                                            node.offsetX = 0
                                            node.offsetY = 0
                                        }
                                    )
                                    .onTapGesture(count: 2){
                                        withAnimation{
                                            showSidebar = true
                                            id = node.id
                                        }
                                    }
                                }
                            }
                        }

                    Button(action:
                            {

                    }) {
                        Text("Save")
                            .fontWeight(.bold)
                            .font(.body)
                            .padding(8)
                            .background(Color.green)
                            .cornerRadius(20)
                            .foregroundColor(.white)
                    }
                    .position(x: UIScreen.main.bounds.width * 69/100, y: 20)

                    Button(action:
                            {
                        nodes.n_array.append(Node(id: nodes.n_array.count, x: 30, y: 30, text: "New", red: 190, green: 190, blue: 190, shape: 1)) // adding new node to nodes array
                        matrix.arr.append(Array(repeating: false, count: nodes.n_array.count)) // adding new row in array
                        for i in (0..<nodes.n_array.count-1)  { // adding new column in array
                                self.matrix.arr[i].append(false)
                        }

                    }) {
                        Text("Add+")
                            .fontWeight(.bold)
                            .font(.body)
                            .padding(8)
                            .background(Color.blue)
                            .cornerRadius(20)
                            .foregroundColor(.white)
                    }
                    .position(x: UIScreen.main.bounds.width * 89/100, y: 20)

        }
    }
}

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

2      

When you use ForEach like this:

ForEach(0..<matrix.arr[id].count)

it creates a static set of data that does not update after you add/remove items.

You are using this initializer on ForEach:

init(_ data: Range<Int>, content: @escaping (Int) -> Content)

If you want dynamic items in your ForEach, you need to use one of the initializers in the Creating a Collection from Data section of the docs.

3      

Two quick comments.

  1. Simplify your view! You provide a lot of redundant view code here that has nothing to do with your issue.
  2. SwiftUI uses declarative structures. You're mixing a lot of procedural programming in your code. This is not very Swifty.

Simplify

Here's a snippet on how to simplify your button decorations. Button decorations have nothing to do with your issue. However these account for a lot of your pasted code. Simplify!

.... snip ....
            Text("Delete").deleteStyle()       //  define your own modifier for button styles.
.... snip ....

// Remove ALL this code from your view! Put into its own structure.
struct DeleteButtonModifier: ViewModifier {
    func body(content: Content) -> some View {
        content
            .font(.body)
            .padding(EdgeInsets(top: 10, leading: 50, bottom: 10, trailing: 50))
            .background(Color.red)
            .foregroundColor(.white)
            .cornerRadius(20)
    }
}

// Create a convenience function. Apply THIS function to your button text.
extension View {
    func deleteStyle() -> some View {
        modifier(DeleteButtonModifier())
    }
}

2      

Procedural vs Declarative

This code seems a bit procedural. It's step-by-step instructions. Consider moving this code into your Nodes class instead.

Button(action: {
            nodes.n_array.remove(at: id)
            if id == nodes.n_array.count {
                id = id - 1
                for x in (id+1)..<nodes.n_array.count
                {
                    nodes.n_array[x].id -= 1
                }
            }
            else
            {
                for x in id..<nodesCount  // nodes.n_array.count
                {
                    nodes.n_array[x].id -= 1
                }
            }
        })

Instead of listing the steps, consider declaring your intentions. Tell SwiftUI what you intend to do when this button is tapped.

Button( action: {
    nodes.removeNode(at: nodeID) // this asks the Nodes() class to perform this function
    }
)

2      

@roosterboy, could you make it more clear, expand it a little bit? I'm a begginer.

2      

Sure. Run the code below in the simulator. Watch what happens when you click the Add Mystery Girl button in the first section versus what happens when you click the button in the second section.

The first section uses an initializer on ForEach that takes an array of Identifiable items and loops through them. That ForEach is dynamic, meaning it will update as you add or delete items to the backing array.

The second section uses an initializer on ForEach that loops through the indices of the array . That ForEach is static, meaning it will be read once and won't update as you add/delete items to the backing array. Also note that you likely get a message like this in the console: ForEach<Range<Int>, Int, Text> count (4) != its initial count (3). ForEach(_:content:) should only be used for *constant* data. Instead conform data to Identifiable or use ForEach(_:id:content:) and provide an explicit id!

import SwiftUI

struct Person: Identifiable {
    let id = UUID()
    let firstName: String
    let lastName: String
}

struct ForEachExample: View {
    @State private var people1 = [
        Person(firstName: "Charlotte", lastName: "Grote"),
        Person(firstName: "Shauna", lastName: "Wickle"),
        Person(firstName: "Mildred", lastName: "Haversham"),
    ]
    @State private var people2 = [
        Person(firstName: "Linton", lastName: "Baxter"),
        Person(firstName: "Jack", lastName: "Finch"),
        Person(firstName: "Sonny", lastName: "Craven"),
    ]

    var body: some View {
        Form {
            Section("Using Identifiable") {
                List {
                    ForEach(people1) { person in
                        Text("\(person.firstName) \(person.lastName)")
                    }
                    Button {
                        people1.append(Person(firstName: "Claire", lastName: "Little"))
                    } label: {
                        Label("Add Mystery Girl", systemImage: "plus.circle")
                    }
                }
            }

            Section("Using Range") {
                List {
                    ForEach(0..<people2.count) { idx in
                        Text("\(people2[idx].firstName) \(people2[idx].lastName)")
                    }
                    Button {
                        people2.append(Person(firstName: "Colm", lastName: "O'Shaunessy"))
                        //and so we can verify that the backing array is updated...
                        print(people2)
                    } label: {
                        Label("Add Mystery Boy", systemImage: "plus.circle")
                    }
                }
            }
        }
    }
}

The version of ForEach that uses a Range<Int> to loop through should only be used when the array is static. For instance, if you use the ForEach to build the available choices in a Picker or something like that, where you won't be adding or removing items.

3      

TAKE YOUR SKILLS TO THE NEXT LEVEL If you like Hacking with Swift, you'll love Hacking with Swift+ – it's my premium service where you can learn advanced Swift and SwiftUI, functional programming, algorithms, and more. Plus it comes with stacks of benefits, including monthly live streams, downloadable projects, a 20% discount on all books, and free gifts!

Find out more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.