This package offers an alternative solution to basic navigation types like navigation links, tab bar, which can be problematic to use or hard to customize if you are trying to go on with the stock solutions.
NavigationComposer is a Swift package. You can add it to your project via Xcode's File -> Swift Packages -> Add package dependency option. The URL is https://github.com/horvathtamasattila/NavigationComposer
The main idea is that all screens you are using are rendered and present at the same time, and with setting currentIndex
, you are moving them horizontally or vertically. Please keep in mind that because of this behavior, onAppear
will not work as you might expect. It will only be triggered when NavigationComposer
loads, and won't be triggered when you navigate to a specific screen within content
.
currentIndex
binding), but you can build here any UI you want. This parameter is optional. If you want to use swipe gestures only to navigate or you are manipulating the binding externally, src="https://raw.github.com/horvathtamasattila/NavigationComposer/master/on't have to use it.1.3.0 update: In earlsrc="https://raw.github.com/horvathtamasattila/NavigationComposer/master/ersion there is a animation
parameter, but it was removed. Use withAnimation
in your logic when changing the index, this way more complex animations won't get mixed up
This is a simple swipe-based pager, no navigation elements are used. Alternatively, you can use alignment: .vertical
for vertical swiping.
import SwiftUI
import NavigationComposer
struct Pager: View {
@State var idx = 0
var body: some View {
NavigationComposer(
screenCount: 7,
currentIndex: $idx,
isSwipeable: true,
content: {
Color.blue
Color.yellow
src="https://raw.github.com/horvathtamasattila/NavigationComposer/master/ Color.green
Color.purple
Color.red
Color.gray
Color.orange
}
)
.edgesIgnoringSafeArea(.all)
}
}
This is an onboarding-like navigation, controlled by a Next
and a Back
button. Also, an example of how to use NavigationComposer
with dynamic screen content.
import NavigationComposer
import SwiftUI
struct Onboarding: View {
@State var idx = 0
let onboardingContent = ["Onboarding 1", "Onboarding 2", "Onboarding 3", "Onboarding 4", "Onboarding 5"]
var body: some View {
NavigationComposer(
screenCount: onboardingContent.count,
currentIndex: $idx,
content: { self.content },
navigation: { self.navigation }
)
.edgesIgnoringSafeArea(.all)
}
var content: some View {
ForEach(onboardingContent, id: \.self) { title in
Text(title)
.font(.largeTitle)
}
}
var navigation: some View {
VStack(spacing: 0) {
Spacer()
Button(
action: { withAnimation(.interactiveSpring()) { self.idx += 1 } },
label: {
Text("Next")
.frame(maxWidth: .infinity, maxHeight: 53)
.font(.headline)
}
)
.foregroundColor(.black)
.background(Color.yellow)
.cornerRadius(8)
.padding(
EdgeInsets(
top: 0,
leading: 15,
bottom: 8,
trailing: 15
))
Button(
action: { withAnimation(.interactiveSpring()) { self.idx -= 1 } },
label: {
Text("Back")
src="https://raw.github.com/horvathtamasattila/NavigationComposer/master/ .frame(maxWidth: .infinity, maxHeight: 53)
.font(.headline)
}
)
.foregroundColor(.black)
.padding(
EdgeInsets(
top: 0,
leading: 15,
bottom: 56,
trailing: 15
))
}
}
}
import NavigationComposer
import SwiftUI
struct TabBar: View {
@State var currentTab: Int = 0
var body: some View {
NavigationComposer(
screenCount: 4,
currentIndex: $currentTab,
content: {
Group {
ContentScreen(title: "Tab1", description: "Check out our books")
ContentScreen(title: "Tab2", description: "Your profile")
ContentScreen(title: "Tab3", description: "Share your favourite book")
ContentScreen(title: "Tab4", description: "Buy some cool books")
}
.edgesIgnoringSafeArea(.top)
},
navigation: {
self.tabBar
}
)
}
var tabBar: some View {
VStack(spacing: 0) {
Spacer()
HStack {
Group {
Spacer()
TabBarButton(label: "Books", image: "book", action: { withAnimation { self.currentTab = 0 } })
Spacer()
TabBarButton(label: "Profile", image: "person.circle", action: { withAnimation { self.currentTab = 1 } })
Spacer()
TabBarButton(label: "Share", image: "square.and.arrow.up", action: { withAnimation { self.currentTab = 2 } })
Spacer()
TabBarButton(label: "Buy", image: "cart", action: { withAnimation { self.currentTab = 3 } })
Spacer()
}
.foregroundColor(.black)
.padding(.bottom, 16)
}
.frame(height: 80)
.background(Color.gray)
.cornerRadius(16)
.padding(.bottom, -16)
Color.gray
.edgesIgnoringSafeArea(.bottom)
.frame(height: 20)
.padding(.bottom, -20)
}
}
}
struct TabBarButton: View {
let label: String
let image: String
let action: () -> Void
var body: some View {
Button(
action: { self.action() },
label: {
VStack {
Image(systemName: image)
Text(label)
.font(.caption)
}
}
)
}
}
struct ContentScreen: View {
let title: String
let description: String
var body: some View {
ZStack {
Color.white
VStack {
Text(title)
.font(.largeTitle)
.padding(.bottom, 40)
Text(description)
.font(.body)
}
}
}
}
It may also be useful to attach your navigation UI in a vertically with your content, so you don't have to do extra calsrc="https://raw.github.com/horvathtamasattila/NavigationComposer/master/ions for your view to be presented correctly. You can use the navigationPosition
parameter for that, with the possible values .top
or .bottom
, so your UI will be attached to the top/bottom of your content in a VStack. The example code is pretty similar to this one as it uses the tab bar example, check the Vertically attached tab bar
section to see more.
import NavigationComposer
import SwiftUI
struct SegmentedControl: View {
@State var page = 0
var body: some View {
ZStack {
Color.orange
.edgesIgnoringSafeArea(.all)
NavigationComposer(
screenCount: 2,
currentIndex: self.$page,
isSwipeable: true,
content: {
Group {
Color.blue
Color.pink
}
.edgesIgnoringSafeArea(.all)
.padding(.top, 80)
},
navigation: {
self.segmentedNavigation
}
)
}
}
var segmentedNavigation: some View {
GeometryReader { geometry in
VStack {
HStack(spacing: 0) {
Text("Page 1")
.frame(width: geometry.size.width / 2, height: 37)
.font(.body)
.foregroundColor(
self.page == 0 ?
Color.yellow :
Color.gray
)
.background(
self.page == 0 ?
Color.black :
Color.white
)
.cornerRadius(8)
.onTapGesture {
withAnimation(.interactiveSpring()) { self.page = 0 }
}
Text("Page 2")
.frame(width: geometry.size.width / 2, height: 37)
.font(.body)
.foregroundColor(
self.page == 1 ?
Color.yellow :
Color.gray
)
.background(
self.page == 1 ?
Color.black :
Color.white
)
.cornerRadius(8)
.onTapGesture {
withAnimation(.interactiveSpring()) { self.page = 1 }
}
}
.frame(width: geometry.size.width - 2 * 16, height: 37)
.background(Color.white)
.cornerRadius(8)
.padding(.top, 8)
.padding(.horizontal, 16)
Spacer()
}
}
}
}
Distributed under the MIT License. See LICENSE
for more information.
link |
Stars: 12 |
Last commit: 2 years ago |
In earlier version there is an animation
parameter, but it was removed. Use withAnimation
in your logic when changing the index, this way more complex animations won't get mixed up.
Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics