user7014451
user7014451

Reputation:

Refactoring out a toolbar to another file

Experienced Swift UIKit coder but definitely not with SwiftUI. Let's say I have the following code:

HStack {
    Button(
        action: { self.createNode() },
        label: {
            Text("ADD").bold().font(.system(size: 40)).frame(width: 200, height: 80).background(Color.yellow).cornerRadius(5)
    })
    Spacer()
    Button(
        action: { self.deleteNode() },
        label: {
            Text("DELETE").bold().font(.system(size: 40)).frame(width: 200, height: 80).background(Color.yellow).cornerRadius(5)
    })
}

A very simple toolbar. But now, let's add (a) four more Buttons and fit this into a "root" view. Makes for what in UIKit we call a massive view controller.

I'm trying to move this entire HStack out of the root view, but I'm having issues with the action. I'm aware I can create ViewModifiers, custom views, and - at least to some degree - move some things into extensions. But I haven't been able to move this extension "lock, stock, and barrel" anywhere else. I'm stuck trying to move createNode() and deleteNode() anywhere else.

I'm sure it's me trying to fit a square peg (UIKit) into a round hole (SwiftUI), but none of the WWDC sessions or other resources I've found seem to be pointing me in the right direction. What am I missing?

EDIT:

Here's what the two actions are - they work properly.

@State var nodes: [Node] = []

func createNode() {
    let newNodeName = nodes.count + 1
    let newNode = Node(name: newNodeName)
    nodes.append(newNode)
}
func deleteNode() {
    if nodes.count != 0 {
        nodes.remove(at: nodes.count - 1)
    }
}

My question is not about Swift, or about maintaining the array. It's about SwiftUI, and how to "refactor" my current 140 line file into smaller things - in this case by removing the "top bar" HStack into it's own file in Xcode.

This horizontal stack of Buttons will eventually number 5 (it's an iPad only app) and my issue is how to move the Button "actions", type () -> Void correctly.

More, here's a simplified view of the entire file (forgive the markdown use, I couldn't find a better way to represent this):

HStack    
    VStack
       HStack  <-- Contains the Buttons that add/delete the nodes
       HStack
       HStack  <-- Contains the nodes themselves, will soon contain thumbnails    
    List

If my HStack consists of a horizontal row of Text views, it works fine. But when I try to move a Button (be it nested in an HStack or standalone) into a separate file, I get build issues.

Upvotes: 1

Views: 658

Answers (1)

Matteo Pacini
Matteo Pacini

Reputation: 22856

I suggest refactoring actions and @State into a view model.

final class ViewModel: BindableObject {

    let didChange = PassthroughSubject<Void, Never>()

    var nodes: [Node] = [] {
        didSet {
            didChange.send(())
        }
    }

    func createNode() {
        let newNodeName = nodes.count + 1
        let newNode = Node(name: newNodeName)
        nodes.append(newNode)
    }

    func deleteNode() {
        if nodes.count != 0 {
            nodes.remove(at: nodes.count - 1)
        }
    }

}

All the views that need access to the actions, need to declare this property:

@EnvironmentObject var viewModel: ViewModel

More info about @EnvironmentObject here.

Whenever the nodes changes, all subviews that declare the environment object will be redrawn.

You only need to set the environment object in the container view, for it to be passed down all the subviews.

e.g.

ContentView().environmentObject(ViewModel())

Your view hierarchy:

HStack    
    VStack
       HStack  <- @EnvironmentObject (will call the actions)
       HStack
       HStack  <- @EnvironmentObject (will use viewModel.nodes to display nodes)
    List

Upvotes: 2

Related Questions