How can I make a bottom sheet appear behind a tabView? - swiftui

Does anyone know how I can create a bottom sheet similar to the one in the Diary Queen app. I have tried a few times but every thing I've tried has made the bottom sheet appear above the bottom tabview. I need it to appear from behind the tabview just like in the screenshots.
I've tried a zstack, but every time my bottom view appears above.
import SwiftUI
struct Menu: View {
#State private var showingBottomSheet = true
var body: some View {
TabView{
orderView()
.tabItem{
Image(systemName: "questionmark")
Text("Order")
}
rewardView()
.tabItem{
Image(systemName: "star")
Text("Rewards")
}
dealsView()
.tabItem{
Image(systemName: "tag")
Text("Deals")
}
myDQView()
.tabItem{
Image(systemName: "person")
Text("My DQ")
}
currentOrderView()
.tabItem{
Image(systemName: "bag")
}
}
.padding()
.sheet(isPresented: $showingBottomSheet) {
Text("hello")
//
}
}
}
struct Menu_Previews: PreviewProvider {
static var previews: some View {
Menu()
}
}

The solution to this, is to not use a .sheet, instead you need to have a view that is built and .offset(y:...) off the screen, and have it watch for your boolean value to change. For example:
struct Menu: View {
#State private var showingBottomSheet = false
var body: some View {
TabView{
Group {
ScrollView{}
.tabItem{
Image(systemName: "questionmark")
Text("Order")
}
Text("")
.tabItem{
Image(systemName: "star")
Text("Rewards")
}
Text("")
.tabItem{
Image(systemName: "tag")
Text("Deals")
}
Text("")
.tabItem{
Image(systemName: "person")
Text("My DQ")
}
Text("")
.tabItem{
Image(systemName: "bag")
}
}
.background(
Color.gray.offset(y: showingBottomSheet ? 0 : UIScreen.main.bounds.size.height - 120)
)
}
}
}
Notice that constructing it like this, puts it behind your view. Also in this example, I set the height - 120 soley so if you copied this, you'll see that the offset is indeed behind the other view. I was also forced to use a ScrollView and replace all the other views with Text because you didn't post those views, the principle should still remain the same.

In the order view, create #state variable and using if statement to display the custom view as a sheet
struct Order: View {
#State var isShow: Bool = true
var body: some View {
ZStack(alignment: .bottom) {
VStack {
Button("Display Sheet") {
isShow.toggle()
}
Spacer()
}
if isShow {
RoundedRectangle(cornerRadius: 20)
.fill(Color.gray.opacity(0.2))
.frame(height: UIScreen.main.bounds.height * 0.8)
.transition(.push(from: .bottom))
.animation(.easeInOut(duration: 1))
}
}
}
}

Related

SwiftUI - Sheet doesn't work in Navigation View

Having issues with a NavigationView and Sheet.
I want to use not full-size sheet but bottom sheet and connect LoginView() and SignView() through sheet. At this time, frame of SignView never follow sheet.
So I tried two ways to solve.
First, LoginView: Has NavigationView out of the sheet and NavigationLink in sheet . But it didn't work.
So I put NavigationView in sheet, it works. But the height of the destination View becomes like sheet.
How can I solve the problem proper way? Thanks!
import SwiftUI
struct LoginView:View{
#State var isPlus : Bool = false
var body: some View{
NavigationView{
VStack(alignment:.center){
Spacer()
ZStack{
Button(action:{
self.isPlus = true})
{
Text("Sign up")
.padding(.horizontal,23)
.padding(20)
.font(.system(size: 25))
.fontWeight(.heavy)
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
}
.sheet(isPresented: $isPlus){
VStack{
NavigationLink(destination:SignView()){
Text("MyCard")
.font(.title)
.foregroundColor(Color.black)
}
}
.presentationDetents([.height(300)])
}
}
}
}
}
}
Hope you can get solution from the below code snippet.
By this way we can use sheet in the NavigationView.
struct LoginView: View {
#State var isPlus : Bool = false
#State private var showingSheet = false
var body: some View {
NavigationView {
VStack(alignment:.center) {
Spacer()
ZStack{
Button(action:{
self.isPlus = true})
{
Text("First Sheet View")
.padding(.horizontal,23)
.padding(20)
.font(.system(size: 25))
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
}
.sheet(isPresented: $isPlus){
VStack{
NavigationView {
NavigationLink(destination:SignView()){
Button(action:{
self.showingSheet = true})
{
Text("Full Sheet View")
.padding(.horizontal,23)
.padding(20)
.font(.system(size: 25))
.background(Color.blue)
.foregroundColor(Color.white)
.cornerRadius(10)
}
.sheet(isPresented: $showingSheet){
Button("Close"){
showingSheet = false
isPlus = false
}
.presentationDetents([.large])
}
}
}
.presentationDetents([.medium])
.edgesIgnoringSafeArea(.all)
}
}
}
}
}
}
}

Closing A View From Another View

I am still learning SwiftUI and I have come across a small problem.
In my app, I have a main view. On the top is a search bar and at the bottom, a menu with different buttons. I want to change views when clicking those buttons. However, I only want to change the middle section.
No big deal, I will just put the middle part into a NavigationView. That works alright and I am able to change my views. My problem is that the buttons below do not have any impact on the new view.
To try to simplify: Let’s say I’m on home page. I then click the grocery list button (guess what I’m making school projects lol). My navigation link works just fine and goes to the list. So, now I’m on view 2 let’s say. When I press the home button, it doesn’t close that view and go to my main one. Here is my code setup:
import SwiftUI
struct ContentView: View {
#State private var searchText: String = ""
#State private var action: Int? = 0
var body: some View {
ZStack {
// Top Menu
VStack{
HStack {
Spacer()
TextField("Search",
text: $searchText)
.background(Color.white)
Button(action: {
self.action = 1
}, label: {
Image(systemName: "magnifyingglass.circle")
.font(.largeTitle)
})
Spacer()
}
// Body
NavigationView {
VStack {
Text("Can I See Something")
NavigationLink(destination: SearchView(), tag: 1, selection: $action) {
}
Text("Yes/No")
}
}
Spacer()
// Bottom Menu
HStack (alignment: .top) {
Spacer()
VStack {
Button(action: {
}, label: {
Image(systemName: "house.fill")
.font(.largeTitle)
})
.padding(.top)
Text("Home")
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "newspaper")
.font(.largeTitle)
})
.padding(.top)
Text("Weekly\nAd")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "checklist")
.font(.largeTitle)
})
.padding(.top)
Text("Grocery\nList")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "person.crop.circle")
.font(.largeTitle)
})
.padding(.top)
Text("Account")
}
Spacer()
}
}
}
}
}
struct SearchView: View {
var body: some View {
ZStack {
Text("Nothing to see here!")
}
.navigationBarBackButtonHidden(true)
}
}
SearchView is a separate view (in its own file) in the app that opens up when the magnifying glass button is pressed. Currently it does not do anything. However I want to be able to press those buttons on this view above to still navigate the app.
Also, on another note, is there anyway to get rid of the back button?
In your code the buttons do not have any function.
Instead of creating a tab bar on your own, I'd rather take something like:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
MainView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
NewsView()
.tabItem {
Label("Weekly\nAd", systemImage: "newspaper")
}
OrderView()
.tabItem {
Label("Grocery\nList", systemImage: "checklist")
}
AccountView()
.tabItem {
Label("Account", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MainView: View {
var body: some View {
Text("Home View")
}
}
struct NewsView: View {
var body: some View {
Text("News View")
}
}
struct OrderView: View {
var body: some View {
Text("Order View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}
In that case you'll have to create a view for each tab you are configuring (see the last 4 structs).
If you want to do it with a Stack with your own created buttons, I think you should create al 4 views as well and then you either hide them or put them out of focus by using an offset. In that case the buttons should hide/show the specific views or change the offset accordingly to move the specific views into the visible area. With the offset you also can add some animation.
Regarding the search bar on top of your app, since the views are all different, I wouldn't keep the same search bar everywhere, but if you really want to have it that way, you can embed the code + your search bar into a VStack (as you did it in your example).

SwiftUI 2.0: Close button is not dismissing the view - how do I get the Close button to return to the previous view?

I have tried to use Buttons and Navigation Links from various examples when researched on this channel and on the net. The NavigationLink would be ok, except that the NavigationView is pushing everything down in my view.
I have a view that contains an image and a text like this: ( x Close) but when I use the code below, the Close button is not doing anything.
In ContentView() I have a (?) button that takes me from WalkthroughView(), then to the PageTabView, then to this view, TabDetailsView:
ContentView():
ZStack {
NavigationView {
VStack {
Text("Hello World")
.padding()
.font(.title)
.background(Color.red)
.foregroundColor(.white)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
showOnBoarding = true
}
} label: {
Image(systemName: "questionmark.circle.fill")
}
}
}
}
.accentColor(.red)
.disabled(showOnBoarding)
.blur(radius: showOnBoarding ? 3.0 : 0)
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
}
.onAppear {
if !isWalkthroughViewShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
showOnBoarding.toggle()
isWalkthroughViewShowing = true
}
}
}
}
WalkthroughView():
var body: some View {
ZStack {
GradientView()
VStack {
PageTabView(selection: $selection)
// shows Previous/Next buttons only
ButtonsView(selection: $selection)
}
}
.transition(.move(edge: .bottom))
}
PageTabView():
var body: some View {
TabView(selection: $selection) {
ForEach(tabs.indices, id: \.self) { index in
TabDetailsView(index: index)
}
}
.tabViewStyle(PageTabViewStyle())
}
below, is the TabDetailsView():
At the top of the view is this Close button, when pressed, should send me back to ContentView, but nothing is happening.
struct TabDetailsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let index: Int
then, inside the body:
VStack(alignment: .leading) {
Spacer()
VStack(alignment: .leading) {
// Button to close each walkthrough page...
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
.padding(.leading)
.font(.title2)
.accentColor(.orange)
Spacer()
VStack {
Spacer()
Image(tabs[index].image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 415)
.padding(.leading, 10)
Text(tabs[index].title)
.font(.title)
.bold()
Text(tabs[index].text)
.padding()
Spacer()
}
.foregroundColor(.white)
}
}
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
Inserting view like above is not a presentation in standard meaning, that's why provided code does not work.
As this view is shown via showOnBoarding it should be hidden also via showOnBoarding, thus the solution is to pass binding to this state into view where it will be toggled back.
Due to deep hierarchy the most appropriate way is to use custom environment value. For simplicity let's use ResetDefault from https://stackoverflow.com/a/61847419/12299030 (you can rename it in your code)
So required modifications:
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
.environment(\.resetDefault, $showOnBoarding)
}
and in child view
struct TabDetailsView: View {
#Environment(\.resetDefault) var showOnBoarding
// .. other code
Button(action: {
self.showOnBoarding.wrappedValue.toggle()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}

SwiftUI Popover Size is not expanding to fit content

Here is my code
struct ContentView: View {
#State var showingPopover = false
var body: some View {
VStack {
Spacer()
Text("Hello World")
Spacer()
HStack {
Spacer()
Button {
self.showingPopover.toggle()
} label: {
Image(systemName: "plus.circle")
}
.popover(isPresented: $showingPopover) {
List(0..<100) { Text("\($0)") }
}.padding(30)
}
}
}
}
This should produce a really nice popover coming from the plus button. But all I get is a really squashed down popover.
Any idea what I am missing here? Is there a way to tell the popover to expand more (without specifying a size)?
You may use a ScrollView and ForEach instead of a List:
struct ContentView: View {
#State var showingPopover = false
var body: some View {
VStack {
Spacer()
Text("Hello World")
Spacer()
HStack {
Spacer()
Button(action: {
self.showingPopover.toggle()
}) {
Image(systemName: "plus.circle")
}
.padding(30)
}
}
// can be attached to the button as well (as in the question)
.popover(isPresented: $showingPopover,
attachmentAnchor: .point(.bottomTrailing),
arrowEdge: .bottom) {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0 ..< 100) {
Text("\($0)")
}
}
}
}
}
You can provide a custom frame for the List. Also, don't forget to embed List inside a ScrollView if you want it to scroll.
ScrollView {
List(0..<100) {
Text("\($0)")
}
.frame(width: 100, height: 250)
}

How do I properly use NavigationView in a ZStack?

I am trying to add some filter options to sit at the top of my view, above the NavigationView. I wrote the following code that mostly does what I want, however it disabled the ability to click on the rows to get to the detailed view. I assume this is because my filter buttons are on top of the ZStack, but I'm not sure how else to get this to work.
Here is the code I wrote:
import SwiftUI
struct BonusList: View {
var bonuses = sampleBonusData
#State var showSettings = false
#State var showBonuses = false
#State var bonusEarned = true
#State var showStatePicker = false
#State var showCategoryPicker = false
var body: some View {
ZStack {
NavigationView {
List(bonuses) { item in
NavigationLink(destination: BonusDetail(bonusName: item.bonusName, bonusCode: item.bonusCode, city: item.city, sampleImage: item.sampleImage)) {
HStack(spacing: 12.0) {
Image(item.sampleImage)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 60, height: 60)
.background(Color.white)
.cornerRadius(15)
VStack(alignment: .leading) {
HStack {
Text(item.bonusName)
.font(.headline)
Spacer()
Image(systemName: "checkmark.shield")
.opacity(self.bonusEarned ? 100 : 0)
}
Text("\(item.city), \(item.state)")
.font(.subheadline)
.frame(height: 25.0)
HStack {
Text(item.bonusCategory)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.gray)
.padding(.top, 4)
Spacer()
Text(item.bonusCode)
.font(.caption)
.fontWeight(.bold)
.foregroundColor(.gray)
.padding(.top, 4)
}
}
}
}
}
.navigationBarTitle(Text("Bonuses"))
// .navigationBarHidden(true)
}
.saturation(self.bonusEarned ? 0 : 1)
HStack {
FilterByCategory(showCategoryPicker: $showCategoryPicker)
Spacer()
FilterByState(showStatePicker: $showStatePicker)
}
StatePicker(showStatePicker: $showStatePicker)
CategoryPicker(showCategoryPicker: $showCategoryPicker)
}
}
}
This is what it looks like when I run it:
If I'm understanding correctly, you have a view or two which sit higher in the ZStack that are off canvas and come in when those buttons are tapped?
You could consider using a modal and setting the view you want to show for each button as the view for the modal. This will keep your views off screen and still allow interaction with your list. Here's what I've done...
On the main view
import SwiftUI
struct MainView: View {
#State private var isPresented = false
var body: some View {
NavigationView {
VStack {
//...
}
//Modal
.sheet(isPresented: $isPresented, content: {
AddItem(showModal: self.$isPresented)
})
}
}
}
The modal's view
import SwiftUI
struct AddItem: View {
#Binding var showModal: Bool
var body: some View {
VStack {
Button(action: {
self.showModal = false
}, label: {
Text("Cancel")
})
}
}
}