SwiftUI Navigation Link containing a Button - swift

I have a SwiftUI list that looks like this:
List {
ForEach(items) { item in
NavigationLink(destination: EditItemView(item: item)) {
ItemView(item: item, buttonAction: {
// Do something
})
}
}
where ItemView is:
struct ItemView: View {
var item: Item
let buttonAction: () -> Void
var body: some View {
HStack(alignment: .center) {
Text(item.tile)
.font(.headline)
Spacer()
Button(action: buttonAction,
label: {
Image(systemName: "plus.circle")
})
.padding(.horizontal)
}
}
}
However, whenever I tap on the button, instead of performing the button action it goes to the EditItemView which is the navigation link action.
So how can I have a button performing an action inside a navigation link?
Or if that is not possible, how can I make that tapping the item view does one action and tapping the button does another action?

Here is possible approach - changed only ItemView by replacing default button with tap gesture (tested with Xcode 12 / iOS 14)
struct ItemView: View {
var item: Int
let buttonAction: () -> Void
var body: some View {
HStack(alignment: .center) {
Text("Item \(item)")
.font(.headline)
Spacer()
Image(systemName: "plus.circle")
.padding(.horizontal).contentShape(Rectangle())
.highPriorityGesture(TapGesture().onEnded(buttonAction))
}
}
}

Related

Entering new view after button press (Swift)

struct EnterOptions: View {
var body: some View {
VStack{
Button(action: {
//Action here
} ){
Text("SIGN UP").frame(maxWidth: 300)
.frame(maxHeight: .greatestFiniteMagnitude)
.font(.largeTitle)
.fontWeight(.heavy)
}
I create a button where I want the user to decide to sign up or login. Just the sign up is shown.
struct makeM: View {
#StateObject var viewModel: makeM.ViewModel = .init()
#StateObject var home: makeM.LoggedIn = .init()
var body: some View {
VStack{
TextField("Username", text: $viewModel.username)
TextField("Password", text: $viewModel.password)
TextField("Email", text: $viewModel.email)
Button("Sign Up", action: {
Task{await viewModel.signUp() }} )
Button("Sign In", action: {
Task{ await viewModel.signIn() }
})
//if(viewModel.si == true){
Button("Sign Out", action: {
Task{ await viewModel.signOutLocally() }
})
//}
}
.padding(.horizontal, 20)
}
I want the user to see this screen after clicking the button. I've tried using "makeM()" or putting MakeM() in a WindowGroup() but not sure how I can switch screens for the user using the right syntax. Having trouble in the "//Action Here" part in the first code snippet. Am I going about this the wrong way? Not trying to use storyboard
You should use NavigationLink if you want to show another screen, but make sure that whole view is wrapped by NavigationStack or NavigationView.
For example:
struct EnterOptions: View {
var body: some View {
NavigationStack {
VStack{
NavigationLink {
// Destination
} label: {
Text("SIGN UP").frame(maxWidth: 300)
.frame(maxHeight: .greatestFiniteMagnitude)
.font(.largeTitle)
.fontWeight(.heavy)
}
}

How can I track scrolling with a ScrollView linked to a Custom PageControl - SwiftUI

I want to create a Carousel with SwiftUI(without using TabView)
with a matching/linked Page Control in SwiftUI
So far I have both views and can update the pageControl view with a
#State var pagecontrolTracker updated with a DragGesture() .onChanged but it doesn't update the PageControl if I scroll fast, or sometimes doesn't update at all 😭.
If I Scroll slow tho, the Page Control does update sometimes as expected.
Is there a better way to update this faster and smoother?
I saw .updating modifier for DragGesture() but this doesn't work either
Full View:
struct ContentView: View {
#State var pagecontrolTracker: Int = 0
var body: some View {
VStack {
ScrollView(.horizontal) {
HStack {
ForEach(0...3, id: \.self) { index in
PagingRow()
.gesture(DragGesture().onChanged({ _ in
pagecontrolTracker = index
}))
}
}
}
PagingControls(pagecontrolTracker: $pagecontrolTracker)
}
.padding()
}
}
Inside Custom SwiftUI Row View
struct PagingRow: View {
var body: some View {
VStack {
HStack {
Image(systemName: "globe")
Text("Test Title")
}
.padding()
Button {
print("Test action")
} label: {
Text("Tap Me")
}
.buttonStyle(.borderedProminent)
.padding()
}
.background(Color.orange)
.frame(width: 200)
.cornerRadius(8)
}
}
Custom PageControl in SwiftUI
struct PagingControls: View {
#Binding var pagecontrolTracker: Int
var body: some View {
HStack {
ForEach(0...3, id: \.self) { pagingIndex in
Circle()
.fill(pagecontrolTracker == pagingIndex ? .orange : .black)
.frame(width: 8, height: 8)
}
}
}
}
Note: I don't want to use TabView since I want to be able to show the next upcoming card in the scrollView
A TabView would only show one card per page

SwiftUI: how to keep single toolbar or ToolbarItem on the next view in NavigationView stack?

Each toolbar applied to the NavigationView destination is in fact separate toolbar, and one toolbar is changing to another with opacity transition.
How to keep one single toolbar or only ToolbarItem on the next destination, so It could be animated for example, let's say trailing ToolbarItem was containing 2 buttons in HStack, and I want to insert another one with animation like I'd do that in one single View?
Want to take a full control over the ToolbarItem like it was a single item.
struct Temp_Preview: PreviewProvider {
static var previews: some View {
return NavigationView {
NavigationLink {
Text("TARGE TPAGE")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolBarView(showExtraButton: true)
}
}
} label: {
Text("Next page")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
ToolBarView(showExtraButton: false)
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct ToolBarView: View {
let showExtraButton: Bool
var body: some View {
HStack {
let buttonsCount = showExtraButton ? 3 : 2
ForEach(0..<buttonsCount, id: \.self) { index in
Button(action: {}) {
Color.blue
.cornerRadius(5)
.frame(width: 80 - CGFloat(20 * index), height: 20)
}
}
}
}
}

Custom action sheet with SwiftUI

I have a TabView at the top of navigation and a tab which contains the user profile.
I want to present an action sheet with custom style when users touch cover/profile picture.
The custom style looks like this:
I already try to attach a second view at the bottom of the ZStack but the TabView always looks in front.
How can I custom the action sheet or how can I hide TabView?
Thanks
ZStack approach will work in this scenario.
I have tried with a small example, you can tweak and try to fit in your project and see if it works.
Implementation :
struct ContentView: View {
#State var showingCustomSheet = false
var body: some View {
ZStack {
VStack {
Button(action: {
withAnimation {
self.showingCustomSheet.toggle()
}
}) {
Text("Show Custom sheet")
}
}
ZStack {
HStack(alignment: .bottom) {
Spacer()
VStack (alignment: .leading, spacing: 10) {
Spacer()
Button(action: {}) { Text("Delete photo") }
Button(action: {}) { Text("Take photo") }
Button(action: {}) { Text("Choose photo") }
Button(action: {
withAnimation {
self.showingCustomSheet.toggle()
}
}) { Text("Cancel") }
}
Spacer()
}
}.background(Color.black.opacity(0.1))
.edgesIgnoringSafeArea(.all)
.offset(x:0, y: self.showingCustomSheet ? 0 : UIApplication.shared.keyWindow?.frame.height ?? 0)
}
}
}

Why does modal view present just once in SwiftUI

I try to use "sheet" modifier to popup a modal view when a List cell was tapped. I find that it's ok in the List Cell View (Modal View could popup multi-times when cell be clicked), but in List View the modal view popups just once.
I have tried using Gesture and Button to trigger the popup, I find that once I use "sheet" modifier to trigger the Modal View, the Result is all the same.
struct ContentView: View {
var body: some View {
List {
ForEach(0..<5) { _ in
ListRow()
}
}
}
}
struct ListRow: View {
#State var showDetail: Bool = false
var body: some View {
HStack(spacing: 20.0) {
Button(action: {self.showDetail = true}) {
HStack {
Text("BigTitle")
.font(.largeTitle)
.fontWeight(.heavy)
Spacer()
Text("SubTitle")
.font(.headline)
.fontWeight(.medium)
.padding(.trailing, 10)
}
}
.sheet(isPresented: self.$showDetail) {
DetailView()
}
}
.frame(width: 320, height: 48)
.padding()
}
}
struct DetailView: View {
var body: some View {
Text("I am Detail View!")
.font(.largeTitle)
.fontWeight(.heavy)
}
}
I had hoped that the modal view could be popped multiple-times in the List View, but in reality the popup occurred just once.
Hope you reply!
Thank you!