NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: Suggestions for fixing the pyramid of doom?

Forums > SwiftUI

Hi everyone!

As a former PM/Designer, I have no industry coding experience - and I've come to realize that my code organization skills are horrible.

I have a SwiftUI view similar to the example below (but far worse), where I have 5-6 if-else statements to validate if certain properties are loaded before presenting the final views.

Somebody suggested that I write helper functions to make it more readable, but then with so many environmentObjects to pass and use in each view, I found that passing classes as objects in helper functions makes it just as complex as simply using nested if loops.

So in a scenario where you have multiple managers to pass around views (like the example below) what would be the best inudstry-convention to fix the pyramid of doom?

import SwiftUI

struct ContentView: View {
    @StateObject var managerA = ManagerA()
    @StateObject var managerB = ManagerB()
    @StateObject var managerC = ManagerC()

    var body: some View {

        if let propertyA = managerA.property {
            if let propertyB = managerB.property {
                if let propertyC = managerC.property {
                    TabView {
                        ViewA()
                        ViewB()
                        ViewC()
                    }
                    .environmentObject(managerA)
                    .environmentObject(managerB)
                    .environmentObject(managerC)
                } else {
                    LoadingView()
                        .task {
                            do {
                                try await self.managerC.getC()
                            } catch {
                                print(error)
                            }
                        }
                }
            } else {
                LoadingView()
                    .task {
                        do {
                            try await self.managerB.getB()
                        } catch {
                            print(error)
                        }
                    }
            }
        } else {
            LoadingView()
                .task {
                    do {
                        try await self.managerA.getA()
                    } catch {
                        print(error)
                    }
                }
        }
    }
}

   

Lucas confesses:

As a former PM/Designer, I have no industry coding experience -
and I've come to realize that my code organization skills are horrible.

I will save the jokes for Dilbert! I am sure there are dozens of great panels that address your situation!

I would encourage you to put your program managment and designer skills to use!

Yes, your code is a mess. So take a step back, and review your GANTT chart. What is your end goal?

Designer: The user wants a fluid experience.
Designer: They want to see a Tab view with three tabs. One for each property.
PM: Well we many not have the data. So it might take a while.
Designer: Nonsense! Don't give me partial data. I will display the tabs when you give me complete data.
PM: Ok, I will assign the data fetching tasks to another worker. They will work in another office.
PM: They won't bother you until they have manager data to display. In the meantime?
Designer: In the meantime, I will display a placeholder.
Designer: I don't want to see ALL the data. Just create a model with a subset of data.
Designer: Post the data on this white board over here. I will use this to update my drawings.
Designer: If you change ANYTHING on this board, I will update my designs.
PM: Agreed. I will assign the data filing and retrieving jobs to another team. You won't see them.
PM: They will only post a subset of the data needed for your views.
PM: We will make the decisions in another office, and only post the data you need for your views.

This is a contrived discussion. But I hope you can see your skills are useful.

One of the lessons you may have missed: SwiftUI is a declarative language. You are mixing procedural logic in with your views.

Let your designer skills guide how the final views should look. Let your designer tell you what data is needed.

Then put your PM to work. Create tasks and subtasks and delegate them to other workers. Don't ask your view (your designer) to do this heavy lifting and decision making. The work of fetching data, organizing data and decision making is best left to a business object. So task the business end of your 'project team' with these tasks.

Stop Coding

Take an evening and stop coding. List all your use cases. Like a PM, list all the tasks necessary to realize your goals. Then create a basic work breakdown diagram. Just like a work breakdown structure, write up tasks to a very low level. Write down the data they need, the function they perform, and the data they produce. Who is the best person to make decisions on manager or property data? I am guessing it's not your designer. So separate your business tasks from your display tasks.

One person fetches the data from the store room. Another person filters the data, only selecting the necessary records. Yet another person is in charge of validating the data, and formatting it in a nice easy way for the designer. The designer take the simplified data, and efficiently applies the desired formatting, colors and paints.

You've got this!

1      

I don't know if this helps?

Here are a few lines of code. Please note how the Designer and the PM work independently. Don't ask the Designer to put business rules in the views. Don't ask the PM to make design decisions. Keep those roles separate as best you can.

class Manager: ObservableObject, Identifiable {
    // PM: This is where all the business rules for
    // Managers belong. The Designer should NOT need to know the rules.
    // The Designer just needs to know how to call Manager functions or
    // how to access Manager data.

    var id = UUID() // Identify each manager uniquely
    @Published var name : String
    @Published var team : String

    init( newManager: String, newTeam: String) {
        name = newManager
        team = newTeam
    }

    // PM defines the rules for changing a manager's team.
    // This is a business rule. It belongs here.
    // It does NOT BELONG in the VIEW code.
    func changeTeam() {
        team = team.split(separator: " ")[0] + " \(Int.random(in: 10...19))"
    }

    // create new managers for testing
    static var managerA = Manager(newManager: "Rey", newTeam: "Rogue 1")
    static var managerB = Manager(newManager: "Han", newTeam: "Falcon" )
}

struct Pyramid: View {
    // The PM starts this task by stating
    // Today, we will be working with these two manager objects.
    // Later, we may add several more managers.
    @StateObject var managerA = Manager.managerA
    @StateObject var managerB = Manager.managerB

    // DESIGNER says: Hand the manager objects to me and
    // I'll lay them out according to these DESIGN rules.
    @State private var selectedManager = Manager.managerA.id
    var body: some View {
        // SwiftUI is declarative.
        // DECLARE what you want to see.
        TabView(selection: $selectedManager) {
            ManagerView(withManager: managerA )
                .tabItem {
                    Label(managerA.name, systemImage: selectedManager == managerA.id ? "figure.wave" : "figure.stand")
                }
                .tag(managerA.id)
            ManagerView(withManager: managerB )
                .tabItem {
                    Label(managerB.name, systemImage: selectedManager == managerB.id ? "figure.wave" : "figure.stand")
                }
                .tag(managerB.id)
            }
    }
}

// DECLARE what you want to see using a Manager Object
struct ManagerView: View {
    // The PM hands the Designer a manager object....
    @ObservedObject var withManager: Manager

    // The Designer does this with the object....
    var body: some View {
        VStack(spacing: 10.0) {
            Text( withManager.name ).font(.largeTitle).foregroundColor(.indigo)
            Text( withManager.team ).font(.subheadline).fontWeight(.bold).foregroundColor(.gray)
                .padding(.bottom)
            Button("Change Team") {
                // Designer should NOT list the steps here in a view!
                // Designer should CALL the business rule and just handle the results.
                withManager.changeTeam() // <- call the rules in the Manager class!
                // Do NOT put business rules here...
                // NO! // withManager.team = withManager.team.split(separator: " ")[0] + " \(Int.random(in: 10...19))"
            }
        }
    }
}

I think your code will be more Swifty if you consider moving your business code OUT of the views. Put your PM skills to work and move the business functions OUT OF the view code.

   

@Obelix to the rescue again! Thank you for the words of wisdom that I will carry for a long time to come.

Although bringing up that I'm an ex-product manager helped me get an answer with great insights, I think it also might have de-focused from what I wanted to ask (apart from my sample code being a little too vague). By "manager" in my sample code, I simply meant a helper class like "LocationManager", not in terms of a role 'manager'!

I tried extending this thread, but since the response Obelix gave is a different type of "solution", I will post another question with a more detailed sample code. Thank you!

   

Hacking with Swift is sponsored by Play

SPONSORED Play is the first native iOS design tool created for designers and engineers. You can install Play for iOS and iPad today and sign up to check out the Beta of our macOS app with SwiftUI code export. We're also hiring engineers!

Click to learn more about Play!

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

Reply to this topic…

You need to create an account or log in to reply.

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.