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)
}
}
}
Related
I am using Tab View in my SwiftUI app. I want the changing of page disabled, while swiping left or right. And I had achieved it from this. This works fine but the issue I am facing is I have a button on the bottom of every view, and when I try to swipe from the button, it is swiping left right. I want to disable it too but don't know how to do it. Here is my code:
struct TabViewTesting: View {
#State var tabSelection = 0
var body: some View {
TabView(selection: $tabSelection) {
firstView().tag(0).contentShape(Rectangle()).gesture(DragGesture())
secondView().tag(1).contentShape(Rectangle()).gesture(DragGesture())
thirdView().tag(2).contentShape(Rectangle()).gesture(DragGesture())
}.tabViewStyle(.page(indexDisplayMode: .never))
}
}
And this is the code for the Views:
extension TabViewTesting {
func firstView() -> some View {
VStack {
Text("First screen")
Spacer()
Button {
self.tabSelection = 1
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to 2nd view")
.foregroundColor(.white)
}
}.padding()
}.background(.green)
}
func secondView() -> some View {
VStack {
Text("second screen")
Spacer()
Button {
self.tabSelection = 2
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to 3rd view")
.foregroundColor(.white)
}
}.padding()
}.background(.red)
}
func thirdView() -> some View {
VStack {
Text("Third screen")
Spacer()
Button {
self.tabSelection = 0
} label: {
ZStack {
RoundedRectangle(cornerRadius: 20)
.frame(height: 50)
Text("move to first view")
.foregroundColor(.white)
}
}.padding()
}.background(.yellow)
}
}
And this is what happening:
I actually found an answer in the comments of this question.
The issue is: any view that has an "onTapGesture" will ignore ".gesture(DragGesture())".
The solution is to use ".simulataneousGesture(DragGesture())" instead to ensure the gesture is capture and handled by both view/modifier.
It worked perfectly in my case after changing it. The only exception is for 2 finger drag gesture.
I can't find the answer to this anywhere, hopefully one of you can help me out.
I have a MainView with some content. And with the press of a button I want to open a DetailView. I am using a ZStack to layer the DetailView on the top, filling the screen.
But with the following code I can't get it to work. The DetailView does not have a transition when it inserts and it stops at removal. I have tried with and without setting the zIndex manually, and a custom assymetricalTransition. Couldn't get that to work. Any solutions?
//MainView
#State var showDetail: Bool = false
var body: some View {
ZStack {
VStack {
Text("Hello MainWorld")
Button(action: {
withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Show detail")
}
}
if showDetail {
ContentDetail(showDetail: $showDetail)
.transition(.move(edge: .bottom))
}
}
.edgesIgnoringSafeArea(.all)
}
And here is the DetailView:
//DetailView
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: { withAnimation(.spring()) {
self.showDetail.toggle()
}
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
.background(Color.yellow)
.edgesIgnoringSafeArea(.all)
}
The result of this code is this:
I'm running Xcode 11.4.1 so implicit animations doesn't seem to work either. Really stuck here, hope one of you can help me out! Thanks :)
Here is a solution. Tested with Xcode 11.4 / iOS 13.4.
struct MainView: View {
#State var showDetail: Bool = false
var body: some View {
ZStack {
Color.clear // extend ZStack to all area
VStack {
Text("Hello MainWorld")
Button(action: {
self.showDetail.toggle()
}) {
Text("Show detail")
}
}
if showDetail {
DetailView(showDetail: $showDetail)
.transition(AnyTransition.move(edge: .bottom))
}
}
.animation(Animation.spring()) // one animation to transitions
.edgesIgnoringSafeArea(.all)
}
}
struct DetailView: View {
#Binding var showDetail: Bool
var body: some View {
VStack (spacing: 25) {
Text("Hello, DetailWorld!")
Button(action: {
self.showDetail.toggle()
}) {
Text("Close")
}
.padding(.bottom, 50)
}
.frame(maxWidth: .infinity, maxHeight: .infinity) // fill in container
.background(Color.yellow)
}
}
I have a NavigationView with a card of Info and then a list, I would like to tap the info and go to another view, or you could tap an item of a list and see more info about an item.
So, if I give 'onTapGesture' modifier to the card view it does print"onTap pressed', but I can't figure our how to make it start another view
var body: some View {
NavigationView {
VStack {
TopResultsCard(games: self.games)
.padding(.top, 10)
.onTapGesture({
print("onTap tapped")
})
ResultsListItem(games: self.games)
}.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.showNewResult.toggle()
}) {
Image(systemName: "plus.circle")
Text("Add result")
}
.sheet(isPresented: $showNewResult) {AddResultView(games: self.games)}
.font(.caption))
}
}
Here is possible approach. Use your destination view instead of demo Text("Next view here") below.
#State private var isActive = false
var body: some View {
NavigationView {
VStack {
TopResultsCard(games: self.games)
.padding(.top, 10)
.onTapGesture { self.isActive.toggle() }
.background(NavigationLink(destination:
Text("Next view here"), isActive: $isActive) { EmptyView() })
ResultsListItem(games: self.games)
}.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.showNewResult.toggle()
}) {
Image(systemName: "plus.circle")
Text("Add result")
}
.sheet(isPresented: $showNewResult) {AddResultView(games: self.games)}
.font(.caption))
}
}
I have a NavigationView and then a VStack. The issue is that everything inside VStack is being displayed in the middle of the screen and not on top. Why is that?
var body: some View {
VStack {
VStack(alignment: .leading) {
if (request?.receiverID ?? "") == userDataViewModel.userID {
Text("\(sendertFirstName) wants to ship your package").font(.title)
} else {
Text("You want to ship \(recieverFirstName)'s package").font(.title)
}
HStack{
Image(systemName: "clock.fill").font(.subheadline)
Text("\(createdAt, formatter: DateService().shortTime)").font(.subheadline).foregroundColor(.secondary)
}
HStack {
VStack(alignment: .leading) {
HStack {
Image(systemName: "person.fill").font(.subheadline)
Text(sendertFirstName).font(.subheadline).foregroundColor(.secondary)
}
}
Spacer()
VStack(alignment: .leading) {
HStack {
Image(systemName: "star.fill")
Text(rating).font(.subheadline).foregroundColor(.secondary)
}
}
}
}
VStack(alignment: .center) {
HStack {
Button("Accept") {
//print(self.request.createdAt)
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "accepted")
}
.padding()
Button("Decline") {
//FirestoreService().respondToRequest(request: self.request.documentReference, status: "declined")
}
.foregroundColor(.red)
.padding()
}
}
ScrollView {
ForEach(messages) { (message: Message) in
if message.senderId == self.userDataViewModel.userID {
HStack {
Spacer()
Text(message.text).font(.subheadline).padding(7.5).background(self.blue).cornerRadius(30).foregroundColor(.white)
}.padding(.bottom, 2)
} else {
HStack {
Text(message.text).font(.subheadline).padding(7.5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Spacer()
}.padding(.bottom, 2)
}
}
}
HStack {
TextField("Message", text: $inputMessage).padding(5).background(self.gray).cornerRadius(30).foregroundColor(.white)
Button(action: {
let msg: [String: Any] = [
"text": self.inputMessage,
"createdAt": Timestamp(),
"senderId": self.userDataViewModel.userID
]
self.reference.updateData([
"messages": FieldValue.arrayUnion([msg])
])
self.messages.append(Message(dictionary: msg))
self.inputMessage = ""
}) {
Image(systemName: "arrow.up.circle.fill").foregroundColor(self.blue).font(.title)
}
}
Spacer()
}
.padding()
.border(Color.red)
}
I want the content to start on top not on the middle. What am I doing wrong here?
You don't need the NavigationView - the way you have everything set up now you are wrapping the VStack in a NavigationView twice and the massive white space is the second empty navigation bar.
By default most Views take only the amount of space they need and they are placed in the center of their parent view. So if you want your content to be placed at the top, you need to add Spacer() as the last element in your VStack:
var body: some View {
VStack {
/* ... */
Spacer()
}
}
Spacer is one of the rare Views that takes all space offered to it by the parent view and will push all of your content upwards.
Maybe you can try this way:
NavigationView {
VStack {
...
}.frame(maxHeight:.infinity, alignment: .top)
.padding()
.border(Color.red).navigationBarTitle("", displayMode: .inline)
}
Add a .navigationBarHidden(true) to your VStack
I don't know how to create an UIToolbar at the bottom of the screen in SwiftUI.
There is no controller in SwiftUI but you can add like this.
var body: some View {
NavigationView {
VStack {
List(model.items) { item in
ItemViewRow(item: item)
}
HStack {
Button(action: {
}) {
Image(systemName: "someimage")
}
Spacer()
Button(action: {
}) {
Image(systemName: "someimage")
}
}.padding()
}
.navigationBarTitle(Text("Items"))
}
}