Reputation: 1
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
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
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.
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.
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)
}
}
}
}
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.
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