3

I'm doing an Apple Watch app, using SwiftUI. The idea is to display different activity content in a single watch ContentView using four individual subviews within the ContentView Body View. I don't want to use a List to display the activity content, but rather multiple custom views within the ContentView.

I would like each individual subview to display unique content from my model.

My model is called QuadActivity and has the following content:

struct QuadActivity: Identifiable {

  let id = UUID()
  var activityImage: Image
  var activityTitle: String
}

I currently have created an extension to QuadActivity as follows, to hold some hardcoded test data:

extension QuadActivity {
  
  static func all() -> [QuadActivity] {
    
     return [
        QuadActivity(activityImage: activityImage1, activityTitle: "activity1"),
        QuadActivity(activityImage: activityImage2, activityTitle: "activity2"),
        QuadActivity(activityImage: activityImage3, activityTitle: "activity3"),
        QuadActivity(activityImage: activityImage4, activityTitle: "activity4")]
  }
}

My ContentView.swift Body view is made up of a VStack with 2 HStacks embedded. Each HStack contains 2 of my subviews with miscellaneous spacers and padding modifiers. Each of the subviews should display the content of one of the array elements from an instance property:

 var activityArrayEntry = QuadActivity.all()

Thus HStack 1 should display activityImage1 and activity1 and activityImage2 and activity2. The other HStack should display the array elements for items 3 and 4.

I can't figure out how to access each of the activityArrayEntry array elements and display each one in one of the subviews.

I was thinking I could use a:

ForEach(activityArrayEntry) { activity in
  VStack and embedded HStack code here}

and display the subview content by looping through the ForEach above.

However, since all my VStack and HStack and subview code is within the ForEach loop, the same array element activity content would be displayed for all subviews because the loop encompasses all the view information for a single pass of the loop. I want each subview to display one of the unique array element's content.

If I move the ForEach within the ZStack and HStack code for each HStack subview display section, it will loop through the array entries, but the loop won't encompass all the subviews code and I won't get all the subviews to display only the activity array content from 1 array element.

Maybe using a ForEach loop is not the way. Is there another way to access the individual array elements from my instance variable such that each unique array element is used only in 1 of the subviews?

Again, how do I get the overall ContentView.swift to display the four subviews within the ZStack and HStack structure so that each subview displays the activity content of only 1 of the array elements.

Here is my ContentView so far. Note a number of commented lines that I will eventually comeback to in order to use an Observed Object model approach from a @Published Observable Object of my model. This will eventually be (maybe) the approach instead of the function all() from my model that I'm using now with hardcoded data to test the data flow in my app... thus the original question/issue.

Note

Call to QuadView() is just a call to an extracted subview where I define the display of the subview (simple Image and Text):

import SwiftUI

struct ContentView: View {
    
//    @ObservedObject var quadViewVM = QuadViewVM()
    var activityArrayEntry = QuadActivity.all()
    
    var body: some View {
        
        ZStack {
            
            HStack {
                
                Divider()
                    .frame(height: 175.0)
                
            }
            .edgesIgnoringSafeArea(.horizontal)
            
            ForEach(activityArrayEntry) { activity in

            VStack {
                
                
                HStack(alignment: .center) {
                    QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
                    
                        //                        NavigationLink(destination: QuadDetail(content: , newActivity: false)) {
                        //
                        //                        }
                        .frame(width: 85.0, height: 100.0)
                        .buttonStyle(PlainButtonStyle())
                    
                    Spacer()
                    
                    QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
                    
                        //                        NavigationLink(destination: QuadDetail()) {
                        //
                        //                        }
                        
                        .frame(width: 85.0, height: 100.0)
                        .buttonStyle(PlainButtonStyle())
                }
                .padding(.horizontal, 15.0)
                .padding(.bottom, -10.0)
                
                Divider()
                
                HStack(alignment: .center) {
                    QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
                    
                        //                        NavigationLink(destination: QuadDetail()) {
                        //
                        //                        }
                        
                        .frame(width: 85.0, height: 100.0)
                        .buttonStyle(PlainButtonStyle())
                    
                    Spacer()
                    
                    QuadView(activityTitle: "\(activity.activityTitle)", activityImage: activity.activityImage)
                    
                        //                        NavigationLink(destination: QuadDetail()) {
                        //
                        //                        }
                        .frame(width: 85.0, height: 100.0)
                        .buttonStyle(PlainButtonStyle())
                }
                    
                .padding([.leading, .bottom, .trailing], 15.0)
                .padding(.top, -10.0)
                    
                    
                .padding(.top, 30.0)
                .edgesIgnoringSafeArea(.horizontal)
                
            }
        }
    }
}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
0

1 Answer 1

2

Instead of static QuadActivity.all() you can store your data in the ViewModel:

class QuadViewVM: ObservableObject {
    @Published var quadActivities: [QuadActivity] = [
        QuadActivity(activityImage: activityImage1, activityTitle: "activity1"),
        QuadActivity(activityImage: activityImage2, activityTitle: "activity2"),
        QuadActivity(activityImage: activityImage3, activityTitle: "activity3"),
        QuadActivity(activityImage: activityImage4, activityTitle: "activity4"),
    ]
}

In your ContentView you can create a grid by using two ForEach loops (as it's a 2D grid):

struct ContentView: View {
    @ObservedObject var quadViewVM = QuadViewVM()
    let columnCount = 2
    var rowCount: Int {
        quadViewVM.quadActivities.count / columnCount
    }

    var body: some View {
        ZStack {
            // Horizontal divider
            VStack {
                Divider()
            }
            .edgesIgnoringSafeArea(.horizontal)
            // Vertical divider
            HStack {
                Divider()
            }
            .edgesIgnoringSafeArea(.vertical)

            activityGrid
        }
    }

    var activityGrid: some View {
        VStack {
            ForEach(0 ..< self.rowCount) { row in
                HStack {
                    ForEach(0 ..< self.columnCount) { column in
                        self.quadView(row: row, column: column)
                    }
                }
            }
        }
    }

    func quadView(row: Int, column: Int) -> some View {
        let activity = quadViewVM.quadActivities[row * columnCount + column]
        return QuadView(activity: activity)
            .frame(width: 85.0, height: 100.0)
            .buttonStyle(PlainButtonStyle())
    }
}

The QuadView is extracted to another function to make it more readable and easier to apply view modifiers.

I'd also recommend passing the whole QuadActivity variable to the QuadView (instead of its single components) - specifically when you need them all:

struct QuadView: View {
    let activity: QuadActivity

    var body: some View {
        ...
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much for your input. I see by using the nested ForEach loops with row and column paraders, your able to target specific array Lee lime I wanted to do. I’ll try this approach and report back.
I used this approach and, with a little .padding() manipulation, this worked. I was also able to move forward and add a NavigationLink to take me to my DetailView. Thank you. Now I'm hoping Apple will announce an easier way to do grids/collection views at WWDC20.
@andyzoom01 FYI, in SwiftUI 2 you can use native Grids, see SwiftUI Lazy Grid Tutorial

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.