SwifUI, iOS 13 show view on top of Navigationview in destination view - ios

I am working on app supporting iOS 13 using SwiftUI, I have custom activity indicator(UIViewRepresentable) on which is displayed on Api call
In main screen where I have created NavigationView, on top of that I am able to add the activity indicator view which is able to cover the navigation bar also,
But in detail view which is navigated using navigation link, I tried the same code but the activity indicator view is displayed below navigation bar(which is not covering the navigation bar)
struct DetailView: View {
#State private var show = false
var body: some View {
ZStack {
Button("showSpinner") {
self.show.toggle()
}
}
.modifier(CoverNavigationBar(show: $show) {
CustomActiviyIndicatorView()
.edgesIgnoringSafeArea(.all)
})
} }
struct CoverNavigationBar<Cover: View>: ViewModifier {
#Binding var show: Bool
let cover: () -> Cover
func body(content: Content) -> some View {
ZStack {
content
if self.show {
cover()
}
}
} }

Related

Present modal view on navigation to another view in swiftui

Let me know if this is a duplicate question.
I am developing an app with a photo picker. I am on view A and there is a + button on it. When I tap on the +, I want to navigate to a view B. Upon navigation I want to present a Photo Picker view automatically inside view B. I am not able to figure out how to do that presentation of the sheet in Swiftui.
In UIKit,on the viewdidappear of viewcontroller B, i would present the Pickerview.
Here's the code that I have
```import SwiftUI
struct ViewB: View {
#State private var showingImagePicker = false
#State private var uploadedPhotos = [UIImage]()
var body: some View {
VStack {
Button("Select Image") {
showingImagePicker = true
}
}
.sheet(isPresented: $showingImagePicker) {
PhotoPicker(photoList: $uploadedPhotos)
}
.onChange(of: uploadedPhotos) { _ in
}
}
}```
This code is ViewB and I will present the pickerView on tapping a button in viewB. but I don't want to tap on button but instead want the picker to show up on appear of ViewB
There is a view modifier in SwiftUI known as .onAppear(perform:), which is the SwiftUI counterpart to UIKit's viewDidAppear and gives you an entry point upon the construction and display of a SwiftUI view. Simply add this modifier to View B (the view inside the sheet that you are presenting). In the closure that you provide to the modifier, you can change the state to present the picker as needed.
If you'd like the picker view to animate in after the view appears, the appropriate place to declare the transition and animation context is on the view acting as your picker view.
struct ViewB: View {
#State private var displayPicker = false
var body: some View {
VStack {
Text("This is View B")
if displayPicker {
PickerView()
.transition(.slide)
.animation(.easeInOut, value: displayPicker)
}
}
.onAppear {
displayPicker = true
}
}
}
Read more about the modifier here:
https://developer.apple.com/documentation/SwiftUI/AnyView/onAppear(perform:)
Think I figured it out -
struct CreateView: View {
#State private var image: Image?
#State private var showingImagePicker = false
#State private var uploadedPhotos = [UIImage]()
var body: some View {
ScrollView {
HStack {
image?.resizable().scaledToFit()
}
.onAppear {
showingImagePicker = true
}
.sheet(isPresented: $showingImagePicker) {
PhotoPicker(photoList: $uploadedPhotos)
}
.onChange(of: uploadedPhotos) { _ in
guard let uiImage = uploadedPhotos.first else {return}
image = Image(uiImage: uiImage)
}
}
}
}
Here PhotoPicker() is the customview that I have

How to hide NavigationBar when the view disappears in SwiftUI?

struct SomeView: View {
var body: some View {
ZStack {
//rest of the code goes here
}.navigationBarTitle("Some View")
}
}
I am trying to hide my view's NavigationBar using .onDisappear{} but it doesn't work and throws a warning. How can I hide my NavigationBar when the view disappears or some condition returns true but not otherwise?
Hide navigation bar view - navigationBarHidden option
struct SomeView: View {
#State private var condition = false
var body: some View {
NavigationView {
Toggle(isOn: $condition) {
Text("Hide NavigationBar View")
}.padding(.horizontal)
.navigationBarTitle("Some View")
.navigationBarHidden(condition)
}
}
}

How to dismiss a presenting view to the root view of tab view in SwiftUI?

I'm using TabView on my home page. Let's just say I have 4 tabs.
On second tab, i can go to another view using NavigationLink and I go to another 2 views using NavigationLink. Then on the latest view, there is a button to present a view and i use .fullScreenCover (since I want to present it full screen).
In the presenting view, I add an X mark on the left side of the navigationBarItems to dismiss. I use #Environment(\.presentationMode) var presentationMode and presentationMode.wrappedValue.dismiss() to dismiss. But it only dismiss the presenting view to the previous view, while actually I want to dismiss it to the root of my view which is the 2nd tab of my TabView.
Is there a way to do this? Because I have looked up to some articles and nothing relevant especially in TabView context.
I also have a question tho:
Is it a right approach to use .fullScreenCover? Or is there another possible solution for example presenting a modal with full screen style (if there's any cause i'm not sure either).
Any suggestions will be very appreciated, thankyou in advance.
The presentationMode is one-level effect value, ie changing it you close one currently presented screen.
Thus to close many presented screens you have to implement this programmatically, like in demo below.
The possible approach is to use custom EnvironmentKey to pass it down view hierarchy w/o tight coupling of every level view (like with binding) and inject/call only at that level where needed.
Demo tested with Xcode 12.4 / iOS 14.4
struct ContentView: View {
var body: some View {
TabView {
Text("Tab1")
.tabItem { Image(systemName: "1.square") }
Tab2RootView()
.tabItem { Image(systemName: "2.square") }
}
}
}
struct Tab2RootView: View {
#State var toRoot = false
var body: some View {
NavigationView {
Tab2NoteView(level: 0)
.id(toRoot) // << reset to root !!
}
.environment(\.rewind, $toRoot) // << inject here !!
}
}
struct Tab2NoteView: View {
#Environment(\.rewind) var rewind
let level: Int
#State private var showFullScreen = false
var body: some View {
VStack {
Text(level == 0 ? "ROOT" : "Level \(level)")
NavigationLink("Go Next", destination: Tab2NoteView(level: level + 1))
Divider()
Button("Full Screen") { showFullScreen.toggle() }
.fullScreenCover(isPresented: $showFullScreen,
onDismiss: { rewind.wrappedValue.toggle() }) {
Tab2FullScreenView()
}
}
}
}
struct RewindKey: EnvironmentKey {
static let defaultValue: Binding<Bool> = .constant(false)
}
extension EnvironmentValues {
var rewind: Binding<Bool> {
get { self[RewindKey.self] }
set { self[RewindKey.self] = newValue }
}
}
struct Tab2FullScreenView: View {
#Environment(\.presentationMode) var mode
var body: some View {
Button("Close") { mode.wrappedValue.dismiss() }
}
}
You have 2 options:
With .fullScreenCover you will have a binding that results in it being presented you can pass this binding through to the content and when the user taps on x set to to false
You can use the #Environment(\.presentationMode) var presentationMode then call presentationMode.wrappedValue.dismiss() in your button body.
Edit:
If you want to unwind all the way you should make the TabView be binding based. I like to use SceneStorage for this take a look at this post then you can access this SceneStorage value anywhere in your app to respond to it but also to update and change the navigation (this also has the benefit of providing you proper state restoration!)
If you make your TabView in this way:
struct ContentView: View {
#SceneStorage("selectedTab") var selectedTab: Tab = .car
var body: some View {
TabView(selection: $selectedTab) {
CarTrips()
.tabItem {
Image(systemName: "car")
Text("Car Trips")
}.tag(Tab.car)
TramTrips()
.tabItem {
Image(systemName: "tram.fill")
Text("Tram Trips")
}.tag(Tab.tram)
AirplaneTrips()
.tabItem {
Image(systemName: "airplane")
Text("Airplane Trips")
}.tag(Tab.airplaine)
}
}
}
enum Tab: String {
case car
case tram
case airplaine
}
Then deep within your app in the place you want to change the navigation you can create a button view.
struct ViewCarButton: View {
#SceneStorage("selectedTab") var selectedTab: Tab = .car
var body: some View {
Button("A Button") {
selectedTab = .car
}
}
}
This will forced the selected tab to be the car tab.
if instead of this you do not want to change tab but rather change what the navigation view is navigated to you can use the same concept for that, NavigationLink that's a binding if this binding is created using a #SceneStorage then in your ViewCarButton you can make changes to it that will change the navigation state.

SwiftUI: How to make a hidden UITabBar display correctly on View appearing?

I have a TabView, with one of the tabs being a NavigationView. I want the tab bar to be hidden on the navigation destination view. I have achieved this, but the view only appears properly after the first rotation. How do I get it to appear properly the first time (2nd image)?
struct ContentView: View {
var rowIndexes : [Int] = [0,1,2,3,4,5,6]
var body: some View {
TabView {
NavigationView {
List {
ForEach(self.rowIndexes, id: \.self) {i in
NavigationLink(
destination: Color(.blue)
.onAppear(perform: {
Global.tabBar!.isHidden = true
})
.onDisappear(perform: {
Global.tabBar!.isHidden = false
})
) {
Text("\(i)")
}
}
}
}.tabItem {
Image(systemName: "list.number")
Text("List View")
}
NavigationView {
Text("Options View")
}.tabItem {
Image(systemName: "wrench")
Text("Options")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Global {
static var tabBar : UITabBar?
}
extension UITabBar {
override open func didMoveToSuperview() {
super.didMoveToSuperview()
Global.tabBar = self
print("Tab Bar moved to superview")
}
}
Here's what the screen looks like after clicking on a link in the list for the first time (INCORRECT, with gap at the bottom where the tab bar would be if it wasn't hidden):
Here's what the screen looks like after rotating it to landscape, then back to portrait (CORRECT, blue View extending all the way to the bottom):
Is there a way to force the redraw, or simulate a rotation and back? I have tried various #State, #EnvironmentObect, and #ObservedObject solutions, but none work.
Adding ignore bottom safe area gives behaviour as you want.
NavigationLink(
destination: Color(.blue).edgesIgnoringSafeArea(.bottom) // << here !!
.onAppear(perform: {
MyGlobal.tabBar!.isHidden = true
Tested with Xcode 11.4 / iOS 13.4.

Navigation of Pop and Push not working in SwiftUI?

I have two screens. First screen has following code
NavigationLink(destination: SecondScreen()) {
Text("Scan Qr Code")
}
The NavigationLink navigates to second screen which works as expected. In the second screen
struct SecondScreen: View {
#Environment(\.presentationMode) var presentation
var body: some View{
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}}
The button in second screen pops to first screen which works as expected, but as soon as I come back to first screen and I click NavigationLink, it does not move to second screen.
Here is a solution. Use a button on the first screen instead of the navigation link and use that button to pop out a sheet. I tested this and it worked for me!
struct ContentView: View {
#State private var showingAddExpense = false
var body: some View {
NavigationView{
Button(action: {
self.showingAddExpense = true
}) {
Text("Button")
}
.sheet(isPresented: $showingAddExpense) {
SecondScreen()
}
}
}
}
struct SecondScreen: View {
#Environment(\.presentationMode) var presentation
var body: some View{
NavigationView{
Button("Done") {
self.presentation.wrappedValue.dismiss()
}
}
}
}

Resources