Marlink
Marlink

Reputation: 1

How to go about a view which can be swiped left/right AND up/down?

I'm learning SwiftUI for iOS and I'm working with XCode 13. How the "heck" to go about swiping my viewport left & right to change different views within the same 'container' or 'collection', but also to allow for swiping up & down, in order to change that primary view? (... to the next or previous one, if they are in a 'feed'.)

Here is a video I created to visualise my situation perfectly: https://youtu.be/6MhFQu0AZx4

*(in the video the red frame is a screen that a user sees, I'm trying to visualise how to navigate between views)

I have a timeline with a list of 'Posts' displayed vertically. I can swipe up & down to navigate to the next post (like TikTok videos, only 1 'Post' is displayed at a time). I can navigate vertically to go to the next or previous 'Post'.

But each 'Post' includes multiple views. And I can swipe left & right to navigate between those views for every Post (something like Insta with multiple media). Each view has a different layout, but all are attached to a 'Post' (e.g. Main Cover view with some image, and then: Comments, Full details, About Author views or more).

How the "heck" should I plan a design of an iOS app for this type of situation? I'm looking for advice, basic information, some direction - not a solution.

I find solutions for swiping vertically and horizontally, but when you can do both - the game changes...

Upvotes: -1

Views: 1227

Answers (2)

Simon W
Simon W

Reputation: 51

Here is a snippet to swipe horizontally from one view to the other :

struct SwipingView: View {
    @State var currentIndex: Int = 0
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView (.horizontal) {
                HStack {
                    ForEach(0..<4) { index in
                        YourView(index: index)
                            .frame(width: UIScreen.main.bounds.width)
                            .id(index)
                            .onAppear {
                                currentIndex = index
                            }
                            .gesture(DragGesture(minimumDistance: 3.0, coordinateSpace: .local)
                                .onEnded { value in
                                    switch(value.translation.width, value.translation.height) {
                                    case (...0, -30...30):
                                        let scrollTarget = index + 1
                                        withAnimation {
                                            proxy.scrollTo(scrollTarget, anchor: .leading)
                                        }
                                    case (0..., -30...30):
                                        let scrollTarget = index - 1
                                        withAnimation {
                                            proxy.scrollTo(scrollTarget, anchor: .leading)
                                        }
                                    default :
                                        break
                                    }
                                })
                    }
                }
            }
        }
    }
}

Upvotes: 0

xTwisteDx
xTwisteDx

Reputation: 2472

First and foremost, in development, you need to learn a critical skill that will carry you forward. That skill is breaking big problems into small problems and working on individual parts one at a time. For example your problem can be broken down into three individual components. This same principal can help you define methods, reusable views, etc. The idea is called "Single Responsibility Principal." which means each and every thing should only do one thing, and do that one thing really well. I could take this code and re-use it anywhere for other views with the right changes.

Break It down

  • Scrollable horizontally
  • Scrollable vertically
  • Each Row needs to support multiple Columns

So Once you've broken it down you need to work one issue at a time. Let's start with the scrollable horizontally.

Horizontal Scrolling

Here is a quick and dirty solution to solve for horizontal scrolling. This is only part of the problem but it sets us up for a foundation to work the rest of the problem.

struct FirstView: View {
    var body: some View {
        ScrollView {
            ForEach(0..<5) { index in
                // NOTE: ANY VIEW can go in here, including collections :D
                Rectangle()
                    .fill(.black)
                    .frame(width: UIScreen.main.bounds.size.width * 0.8,
                    height: UIScreen.main.bounds.size.height * 0.8)
            }
        }
    }
}

Adding Vertical Scrolling

The next part of the problem is to add some vertical scrolling. With our ForEach loop we previously defined we can add any view inside of each "Section" so to speak, so we could add another scrolling view.

struct FirstView: View {
    var body: some View {
        ScrollView(.vertical) {
            ForEach(0..<5) { _ in
                // NOTE: Notice I just added another ScrollView and changed it's direction to horizontal.
                ScrollView(.horizontal) {
                    LazyHStack {
                        ForEach(0..<5) { _ in
                            Rectangle()
                                .fill(.black)
                                .frame(width: UIScreen.main.bounds.size.width * 0.8,
                                height: UIScreen.main.bounds.size.height * 0.8)
                        }
                    }
                }.frame(height: UIScreen.main.bounds.height)
            }
        }
    }
}

Another thing to notice here is on my inner ForEach loop I used a LazyHStack which tells it to layout those views horizontally, which is a requirement to have them laid out in that direction. To boot it also helps save memory because the views will only be loaded when they are going to be presented. You can also wrap the outer in a LazyVStack for the same effect.

Make it accept differing amount of views

This final piece has a ton of leeway to handle it the way you want and you're likely going to want to add in some QOL things or Quality of Life changes, eg snapping to a column/row, centering, etc but I'll leave that up to you to search up as there's tons of information available on the internet for that stuff. Notice that I'm using 0..<5 for the ForEach loops. That is the value you're going to want to change. I recommend using an [[<someObject>]] to manage that. That's an array of arrays if you're confused. The reason for that is the outer array object could contain your Rows where the inner array could contain your Columns and it makes going through them infinitely easier to manage and track. An example object might look something like this.

var posts: [[Post]] = [[Post1, Post2, Post3],
                       [Post1, Post2, Post3]]

Which would return two rows with three columns each. And you could plug the count of that into your ForEach in some fashion like this. ForEach(0..<posts.count) and then on the inner ForEach(0..<posts[index].count).

I hope this helps in some small way, and good luck!

Upvotes: 2

Related Questions