I have several vertically-stacked two pairs of rectangles (Rectangle()), each of which has a Button. When the user taps a button, the user needs to be forwarded to a specific View. For instance, if they tap Alice, the user needs to be forwarded to AliceView.
If it's just the matter of one button, forwarding the user from the current View to another isn't a problem. Anyway, the following is what I have.
import SwiftUI
struct ContentView: View {
#State var canAlice: Bool = false
#State var canKim: Bool = false
var body: some View {
NavigationView {
List {
HStack(spacing: 0) {
// ZStack: A view that overlays its children, aligning them in both axes.
ZStack {
Rectangle().frame(width: UIScreen.screenWidth / 2.0, height: UIScreen.screenWidth / 2.0, alignment: .topLeading)
.foregroundColor(.orange)
.border(Color.yellow, width: 2)
Button(action: {
self.canAlice = true
}, label: {
Text("Alice")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
})
NavigationLink(destination: AliceView(), isActive: $canAlice) { EmptyView() }
}
ZStack {
Rectangle().frame(width: UIScreen.screenWidth / 2.0, height: UIScreen.screenWidth / 2.0, alignment: .topLeading)
.foregroundColor(.orange)
.border(Color.yellow, width: 2)
Button(action: {
self.canKim = true
}, label: {
Text("Kim")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
})
NavigationLink(destination: KimView(), isActive: $canKim) { EmptyView() }
}
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
}
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
}
}
}
Now, if I tap the Alice button, the current View transitions to AliceView, comes back to the current View and then transitions to KimView. How can I have multiple buttons within a single View and forward the user to respective Views? I'm new to SwiftUI. Thanks.
UPDATE
import SwiftUI
struct ContentView: View {
#State private var selection: Int? = 0
var body: some View {
NavigationView {
List {
HStack(spacing: 0) {
// ZStack: A view that overlays its children, aligning them in both axes.
ZStack {
Rectangle().frame(width: UIScreen.screenWidth / 2.0, height: UIScreen.screenWidth / 2.0, alignment: .topLeading)
.foregroundColor(.orange)
.border(Color.yellow, width: 2)
Button(action: {
self.selection = 1
}, label: {
Text("Alice")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
})
NavigationLink(destination: AliceView(), tag: 1, selection: self.$selection) { EmptyView() }
}
ZStack {
Rectangle().frame(width: UIScreen.screenWidth / 2.0, height: UIScreen.screenWidth / 2.0, alignment: .topLeading)
.foregroundColor(.orange)
.border(Color.yellow, width: 2)
Button(action: {
self.selection = 2
}, label: {
Text("Kim")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
})
NavigationLink(destination: KimView(), tag: 2, selection: self.$selection) { EmptyView() }
}
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
HStack(spacing: 0) {
...
...
...
}.listRowInsets(EdgeInsets())
}
.edgesIgnoringSafeArea(.all)
.statusBar(hidden: true)
}
}
}
Now, when I tap Alice or Kim, I won't transition to another view and then bounce back to the initial view. Whether I tap Alice or Kim, I transition to AliceView.
Here is a solution (by approach referenced in comment). Tested with Xcode 11.4 / iOS 13.4
Button style to hide a link:
struct LinkButtonStyle<D: View>: ButtonStyle {
let destination: D
#Binding var isActive: Bool
func makeBody(configuration: Configuration) -> some View {
configuration.label
.background(NavigationLink(destination: self.destination, isActive: $isActive) { EmptyView() })
}
}
and an example of modified cell
ZStack {
Rectangle().frame(width: UIScreen.screenWidth / 2.0, height: UIScreen.screenWidth / 2.0, alignment: .topLeading)
.foregroundColor(.orange)
.border(Color.yellow, width: 2)
Button(action: {
self.canAlice = true
}, label: {
Text("Alice")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
}).buttonStyle(LinkButtonStyle(destination: AliceView(), isActive: $canAlice))
}
You could achieve this using a single Bool for isActice parameter and a single NavigationLink. Also need to add a destination parameter outside the body. And set the destination in the button action. Here's an example:
struct ContentView: View {
#State var showNextView = false
var destination = GeorgeView()
var body: some View {
NavigationView {
List {
HStack(spacing: 0) {
NavigationLink(destination: destination, isActive: $showNextView) { EmptyView() }
// Rest of your implementation...
Button(action: {
self.destination = KimView()
self.showNextView = true
}, label: {
Text("Kim")
.font(.largeTitle)
.background(Color.black)
.foregroundColor(.white)
})
}
}
}
}
}
Related
I have an image slider with dot indicator named "Onboardingslider". I have used it in my another screen "onboard4" , I want to change the text of "next" button to "get started" only on last image of slider otherwise it stays "next".
I have tried a lot of things but nothing works
Please help me I'm a newbie
struct onboard4: View {
#State var showModal = false
#State var maxlogoheight: CGFloat = 0
#State var isLinkActive = false
var body: some View {
NavigationView {
ZStack{
//max height will be width of the screen
GeometryReader{ proxy -> AnyView in
let height = proxy.frame(in: .global).height
DispatchQueue.main.async {
if maxlogoheight == 0 {
maxlogoheight = height
}
}
return AnyView (
ZStack{
Image("Logo")
.resizable()
.scaledToFit()
.offset(x: getReact().width/3.5, y: -height/1.25)
}//zstack 2
// .padding(.leading,10)
)//anyview
}//end of gr
.frame(maxHeight: getReact().width)
VStack{
Onboardingslider()
Button(action: {
showModal = true
}) {
ZStack{
Text("Next")
.foregroundColor(Color.white)
.fontWeight(.bold)
.frame (width: 295, height: 30)
.padding()
.background(Color("YellowAccent"))
.cornerRadius(20)
.shadow(color: .gray, radius: 5, x: 0, y: 4)
Image("NextButtonOnboard")
}
}
.fullScreenCover(isPresented: $showModal) {
LoginView() }
.offset(y: getSafearea().bottom + -55)
}//vstack
Button(action: {}, label: {
Text("Continue to the listing service")
.underline()
})
.foregroundColor(Color.black)
.offset(y: getSafearea().bottom + 310)
}//zstack
.background(
NavigationLink(destination: LoginView(), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
showModal = true
}) {
Text("Skip").underline()
}
.foregroundColor(Color("YellowAccent"))
.font(.system(size: 20,weight: .semibold,design: .serif))
.frame(width: 100, height: 100)
.padding(.top)
.fullScreenCover(isPresented: $showModal) {
LoginView() }
//.padding(.bottom)
}//toolbaritem
}//toolbar
}
}
}
struct onboard4_Previews: PreviewProvider {
static var previews: some View {
onboard4()
}
}
struct Onboardingslider: View {
private let images = ["1", "2", "3", "4"]
init() {
// modify appearance
UIPageControl.appearance().currentPageIndicatorTintColor = .orange
UIPageControl.appearance().pageIndicatorTintColor = .gray
}
var body: some View {
TabView {
ForEach(images, id: \.self) { item in
Image(item)
.padding(.leading,24)
}
}
.frame(height: 600)
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
}
}
I am having a bug while adding an Object (Slider Card -- Photo of slider Card) on another Object (The background, and the map) What it does is stack 2 objects vertically (The bug), I assume it is because I use VStack. Is there a function that I can use to make the slider in front of the background? Thank you in advance.
import SwiftUI
struct ContentView: View {
let locations = ["Subaru WRX", "Tesla Model 3", "Porsche 911", "Renault Zoe", "DeLorean","Tony's Car"]
#State private var searchText : String = ""
var view = UILabel(frame:CGRect(x: 0, y: 0, width: 335, height: 29))
#State var slideBar = CGSize.zero
var body: some View {
VStack {
Label(
title: {Text("EcoFit")},
icon: {Image("logo")
.resizable()
.frame(width: 128.0, height: 64.0)
}
).labelStyle(IconOnlyLabelStyle())
SearchBar(text: $searchText)
HStack {
Spacer()
Button(action: {
}){
Text("Bike")
}
Spacer()
Button(action: {
}){
Text("Bus")
}
Spacer()
Button(action: {
}){
Text("Scooter")
}
Spacer()
Button(action: {
}){
Text("Walk")
}
Spacer()
}
List{
ForEach(self.locations.filter {
self.searchText.isEmpty ? true : $0.contains(self.searchText)
}, id: \.self) { car in Text(car)
}
}
.searchable(text: $searchText){
ForEach(self.locations.filter {
self.searchText.isEmpty ? true : $0.contains(self.searchText)
}, id: \.self) { result in Text("Are you looking for \(result)").searchCompletion(result)
}
}
Card() <----- slider card here!!
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.background(Color.white)
}
}
I wanted to make a bottomsheet in SwiftUI with my own efforts, I open it using animation, but my animation doesn't work when closing, what is the reason?
I wonder if the offset value is increasing with animation, is there a problem while it is decreasing I am not very good at SwiftUI so I could not fully understand the problem.
struct ContentView: View {
#State var isOpen = false
#State var offset = UIScreen.main.bounds.height / 3
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
Button(action: {
self.isOpen.toggle()
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.black)
Text("Open")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
}
})
.buttonStyle(DefaultButtonStyle())
.frame(width: 300, height: 50, alignment: .center)
if isOpen {
GeometryReader { geometry in
VStack {
Spacer()
BottomSheet()
.frame(width: geometry.size.width,
height: geometry.size.height / 3,
alignment: .center)
.background(
Color.white
)
.offset(y: offset)
.onAppear(perform: {
withAnimation {
self.offset = 0
}
})
.onDisappear(perform: {
withAnimation {
self.offset = UIScreen.main.bounds.height / 3
}
})
}.ignoresSafeArea()
}
}
}
}
}
BottomSheet
struct BottomSheet: View {
var body: some View {
Text("Hello, World!")
}
}
onDisappear gets called when the view was removed, that's the reason custom animation not working :
struct ContentView: View {
#State var isOpen = false
var offset: CGFloat {
isOpen ? 0 : UIScreen.main.bounds.height / 3
}
var body: some View {
ZStack {
Color.blue
.ignoresSafeArea()
Button(action: {
self.isOpen.toggle()
}, label: {
ZStack {
RoundedRectangle(cornerRadius: 25.0)
.foregroundColor(.black)
Text("Open")
.font(.title2)
.fontWeight(.bold)
.foregroundColor(.white)
}
})
.buttonStyle(DefaultButtonStyle())
.frame(width: 300, height: 50, alignment: .center)
GeometryReader { geometry in
VStack {
Spacer()
BottomSheet()
.frame(width:geometry.size.width,
height: geometry.size.height / 3,
alignment: .center)
.background(
Color.white
)
.offset(y: offset)
.animation(.easeInOut(duration: 0.5)) .transition(.move(edge: .bottom))
} .edgesIgnoringSafeArea(.bottom)
}
}
}
}
I'm developing a iOS app relies on SwiftUI.
I have a ZStack view and inside it, I call a another view along with a button.
ZStack(alignment: .bottomTrailing) {
ImageStepView(data: self.data[randomImageNum])
Button(action: { self.showFavorites = true }) {
HStack {
Image(systemName: "suit.heart.fill")
Text("FAVORITES")
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 15)
.foregroundColor(.white)
.padding()
.background(Color.black.opacity(0.6))
.cornerRadius(5)
.padding(.horizontal, 20)
}
}
ImageStepView.swift
struct ImageStepView: View {
var data: ImageDataModel
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
VStack{
Image(data.image)
.resizable()
.border(Color.white, width: 5)
.overlay(
Rectangle().stroke(Color.white, lineWidth: 4))
.shadow(color: Color.gray, radius: 10, x: 10, y: 10)
.scaledToFit()
.frame(height: geometry.size.height-110)
} .padding()
VStack{
VStack(alignment: .leading) {
HStack{...}
HStack {...}
}
Spacer()
.frame(height: 50)
VStack(alignment: .leading){
VStack{
HStack {...}
HStack {...}
HStack {...}
}
}
}.padding()
}.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
ImageStepView has a ScroolView, that's why Button not appears on the end of ScroolView, it appears on bottom of the screen.
What I want is to show The Button not on bottom of the screen but end of the ImageStepView.
You can make the ImageStepView accept a generic parameter - a view to be injected:
struct ImageStepView<Injected: View>: View {
var data: ImageDataModel
var injectedView: () -> Injected
var body: some View {
GeometryReader { geometry in
ScrollView(.vertical) {
// ScrollView contents
injectedView()
}
.background(Color("Color").ignoresSafeArea(.all))
.frame(width: geometry.size.width)
.frame(minHeight: geometry.size.height)
}
}
}
and pass the injected view to the ImageStepView:
ImageStepView(data: self.data[randomImageNum]) {
Button(action: { self.showFavorites = true }) { ... }
}
or
ImageStepView(data: self.data[randomImageNum]) { EmptyView() }
I have 2 views: PollCard and PollList (like list of polls)
In the PollCard view I have 2 buttons(images), that calls "answer" function:
HStack{
Button(action: {
self.answer()
print("Pressed first image")
}){
Image(poll.v1img)
.resizable()
.renderingMode(.original)
.scaledToFill()
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200)
Button(action: { self.answer()}){
Image(poll.v2img )
.resizable()
.renderingMode(.original)
.frame(width: 150, height: 200)
}.frame(width: 150, height: 200).zIndex(4)
}
In the PollList view I have this simple list:
var body: some View {
HStack{
List(pollData) { poll in
PollCard(poll: poll)
}.padding()
}
}
But when I click the images in the list, it selects like all images and presses it
It is also very easy to check - terminal prints Pressed first image even if I've pressed only second image
What should I do to fix this?
As I mentioned in the comment section the workaround would be to substitute the HStack around the List with a ScrollView and the List with a ForEach:
struct ContentView: View {
struct Data: Identifiable {
var id: Int
}
#State var data = [Data(id: 0), Data(id: 1), Data(id: 2), Data(id: 3), Data(id: 4), Data(id: 5)]
var body: some View {
ScrollView {
ForEach(self.data) { data in
HStack {
Button(action: {
print("Pressed blue...")
}, label: {
Rectangle()
.foregroundColor(Color.blue)
.frame(width: 150, height: 200)
})
Button(action: {
print("Pressed red...")
}, label: {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 150, height: 200)
})
}
}
}
}
}
I hope this helps!
this is an unexpected behavior of Button (or it is a bug?) in current SwiftUI (either on macOS or iOS).
The workaround in your case is simple, try to apply PlainButtonStyle for your buttons
import SwiftUI
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}.buttonStyle(PlainButtonStyle())
Button(action: {
print("button2")
}) {
Color.green
}.buttonStyle(PlainButtonStyle())
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The funny thing is that it is enough to apply the style on one button only ... or apply it to parent HStack :-)
changing ContentView ...
struct ContentView: View {
var body: some View {
List {
Text("Hello, World!")
HStack {
Button(action: {
print("button1")
}) {
Color.yellow
}
Button(action: {
print("button2")
}) {
Color.green
}
}
.buttonStyle(PlainButtonStyle())
.frame(height: 100)
Text("By by, World!")
}
}
}
you get
where each of buttons works as expected