How to make modal non-dismissible in SwiftUI - user-interface

I’m creating an app where I have create first screen (it will be short description of application) and on the screen I have a next Button if I click on next button it should be dismiss otherwise it must not be dismiss either pull down.
If user pull down a sheet, it should be again re-position.
The problem is, that the user can dismiss the modal by swiping it down and application dashboard screen show that should be prevented.
How can we prevent to dismiss the Model by pull down.
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(width: 400, height: 650)
.overlay(
VStack{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "chevron.left")
Text("Dismiss")
}.padding(10.0)
.overlay(
RoundedRectangle(cornerRadius: 10.0)
.stroke(lineWidth: 2.0)
)
}.accentColor(.white)
})
.border(Color.blue)
.gesture( DragGesture())
}
}
ContentView
struct ContentView: View {
//MARK: Properties
//isPresented:- Present's a Welcome Screen in the form of cards.
#State private var isPresented = true
var body: some View {
VStack{
DashboardView()
.sheet(isPresented: $isPresented){
//IntroductionView(isPresentingSheet: self.$isPresented)
ModalView()
}
}
}
}
DashboardView
struct DashboardView: View {
var body: some View {
Text("Hello SwiftUI")
}
}

You can try this solution:
struct ModalWrapper: View {
var body: some View {
ModalView().highPriorityGesture(DragGesture())
}
}
struct ModalView : View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(width: 400, height: 650)
.overlay(
VStack{
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "chevron.left")
Text("Dismiss")
}.padding(10.0)
.overlay(
RoundedRectangle(cornerRadius: 10.0)
.stroke(lineWidth: 2.0)
)
}.accentColor(.white)
})
.border(Color.blue)
.highPriorityGesture(DragGesture())
}
}
struct ContentView: View {
//MARK: Properties
//isPresented:- Present's a Welcome Screen in the form of cards.
#State private var isPresented = true
var body: some View {
VStack{
DashboardView()
.sheet(isPresented: $isPresented){
//IntroductionView(isPresentingSheet: self.$isPresented)
ModalWrapper()
}
}
}
}
struct DashboardView: View {
var body: some View {
Text("Hello SwiftUI")
}
}
Here I have added ModalWrapper for wrap the modal view Or else you will have to add highPriorityGesture(DragGesture()) to all subviews of the ModalView So it is better to keep one wrapper view.
Hope this will help you.

Related

SwiftUI: Why toolbar items added from any view appear only in the main toolbar in macOS?

In my macOS app (SwiftUI project multiplateform), I have a main toolbar that works well.
However, when I loaded a view, in a popover for example, the toolbar items set in the popover view, are added to the main toolbar.
In this image above, the X icon visible at top belongs to the popover view. It's added when loading the popover and hidden when leaving the popover.
The toolbar code in the popover view:
toolbar {
Button(action: {
selectedItemId = nil // set
},
label: { Image(systemName: "x.circle") })
}
Adding a searchable field will also be added in the main toolbar, and can break completly the toolbar layout.
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always)) {}
How macOS controls the different toolbar items from different views ? Each new view shoulb be a "window" to control a new toolbar attached to it ?
Thanks in advance.
.popover does not support Toolbars.
And in a Sheet the toolbar items are placed at the bottom.
But you can always include your own controls and buttons in a Popover. Just don't use .toolbar:
struct ContentView: View {
#State private var showSheet = false
#State private var showPopover = false
var body: some View {
VStack {
Button("Show sheet") { showSheet = true }
Button("Show popover") { showPopover = true}
}
.toolbar {
Button {
} label: {
Image(systemName: "house")
}
}
.sheet(isPresented: $showSheet) {
VStack {
Text("Dummy Sheet")
List(0..<20) { i in
Text("item \(i)")
}
.toolbar {
Button {
} label: {
Image(systemName: "x.circle")
}
}
}
.padding()
.frame(minWidth: 200, minHeight: 200)
}
.popover(isPresented: $showPopover) {
VStack {
Text("Dummy Popover")
List(0..<20) { i in
Text("item \(i)")
}
Divider()
Button {
} label: {
Image(systemName: "x.circle")
}
}
.frame(minWidth: 200, minHeight: 200)
.padding(.vertical)
}
}
}

SwiftUI Error - Type () cannot confirm to view: only struct/enum/class types can conform to protocols

I've got some simple code that creates an array (newFactoid) by calling a function (createSampleData) which it then holds as a State. A view displays record 1 of the array. So far so good. However, I'm trying to insert a button into the view which calls a simple function (refreshFactoid) which shuffles the array, in theory causing the view to refresh. The problem is I'm getting the above error when I insert the button/function. If I remove the button the error disappears. Any pointers/help appreciated.
import SwiftUI
struct ContentView : View {
#State private var newFactoid = createSampleData()
var body: some View {
VStack {
// Display Category
Text(newFactoid[1].category).fontWeight(.thin)
.font(.title)
// Display Image
Image("Factoid Image \(newFactoid[1].ref)")
.resizable()
.scaledToFit()
.cornerRadius(15)
.shadow(color: .gray, radius: 5, x:5, y:5)
.padding(25)
// Display Factoid
Text("A: \(newFactoid[1].fact)")
.padding(25)
.multilineTextAlignment(.center)
.background(Color.white)
.cornerRadius(15)
.shadow(color: .gray, radius: 5, x:5, y:5)
.padding(25)
// Display Odds
Text("B: \(newFactoid[1].odds)").fontWeight(.ultraLight)
.font(.title)
.padding()
.frame(width: 150, height: 150)
.clipShape(Circle())
.multilineTextAlignment(.center)
.overlay(Circle().stroke(Color.white, lineWidth: 2))
.shadow(color: .gray, radius: 5, x: 5, y: 5)
// Refresh Button
Button (action: {refreshFactoid()}) {
Text("Press To Refresh Data")
}
// Refresh Function
func refreshFactoid() {
newFactoid.shuffle()
}
} // End of VStack Closure
}
}
struct TextUIView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The func cannot be declared in the VStack.
Either trigger the shuffle in the action block of the Button:
Button(action: { self.newFactoid.shuffle() }) {
Text("Press To Refresh Data")
}
or declare the function in the View struct:
struct ContentView: View {
#State private var newFactoid = createSampleData()
var body: some View {
VStack {
// ...
// Refresh Button
Button(action: { self.refreshFactoid() }) {
Text("Press To Refresh Data")
}
} // End of VStack Closure
}
// Refresh Function
func refreshFactoid() {
newFactoid.shuffle()
}
}
just remove
// Refresh Function
func refreshFactoid() {
newFactoid.shuffle()
}
} // End of VStack Closure
from the body

Navigation bar Display Mode is not working Perfectly in SwiftUI

I'm using SwiftUI with Version 11.3.1 (11C504).
I Implement Tab bar on the Screen and that screen will come after the navigation from login Screen.
If navigation displayMode is .automatic so i’am showing a white space(which is in bordered with black line in picture) when I scroll a list in up side. Otherwise navigation title show.
And When I set displayMode, .inline so it works perfect. And when I run a project so many time so some time it works perfect and some time showing a space.
i mentioned a white space in the picture with black border.
//ContentView
struct ContentView: View {
var body: some View {
HomeTabView()
}
}
//HomeTabView
struct HomeTabView: View {
#State private var selection = 0
//Inclose user intraface of tab View.
var body: some View {
TabView(selection: $selection){
TestListView().tabItem {
Image(systemName: "book.fill")
Text("Learn")
}.tag(0)
Text("Community").tabItem {
Image(systemName: "globe")
Text("Community")
}.tag(1)
//Add Notification List on the Screen.
Text("Notification").tabItem {
Image(systemName: "bell.fill")
Text("Notification")
}.tag(3)
//Add Account on the Tab Bar
Text("Account").tabItem {
Image(systemName: "person.circle.fill")
Text("Account")
}.tag(4)
}.accentColor(.pink)
.navigationBarTitle("SwiftUI")
}
}
TestListView
struct TestListView: View {
var body: some View {
VStack{
List(1...10, id: \.self){ num in
ListCards()
}
}.edgesIgnoringSafeArea(.all)
}
}
//ListCards
struct ListCards: View {
var body: some View {
ZStack{
RoundedRectangle(cornerRadius: 16)
.frame(height: 180)
.foregroundColor(.white)
.shadow(radius: 5)
VStack(alignment: .leading, spacing: 10){
HStack(alignment: .top){
Rectangle()
.frame(width: 100, height: 100)
.cornerRadius(16)
.foregroundColor(.pink)
VStack(alignment: .leading, spacing: 4){
Text("SwiftUI")
.font(.title)
Text("Description of title")
.foregroundColor(.gray)
}
}
.padding()
}.padding(.leading, 2)
}.padding(.all, 6)
}
}
You need to change your HomeTabView in this way:
var body: some View {
TabView(selection: $selection) {
NavigationView {
TestListView()
.navigationBarTitle("SWIFTUI")
}.tabItem {
Image(systemName: "book.fill")
Text("Learn")
}.tag(0)
NavigationView {
Text("Community")
}.tabItem {
Image(systemName: "globe")
Text("Community")
}.tag(1)
//Add Notification List on the Screen.
NavigationView {
Text("Notification")
}.tabItem {
Image(systemName: "bell.fill")
Text("Notification")
}.tag(3)
//Add Account on the Tab Bar
NavigationView {
Text("Account")
}.tabItem {
Image(systemName: "person.circle.fill")
Text("Account")
}.tag(4)
}.accentColor(.pink)
}
also, remove .edgesIgnoringSafeArea(.all) from TestListView

Animating a View by its height in SwiftUI

I am attempting to make a view which will animate another content view in from the bottom of the screen. The below code works, however, as the content view will have unknown height the 200 offset may not be correct. How can I get the height of the content in order to offset the view correctly?
struct Test<Content>: View where Content : View {
#State var showing: Bool = false
var content: Content
var body: some View {
VStack {
Button(action: {
withAnimation {
self.showing.toggle()
}
}) {
Text("Toggle")
}
Spacer()
HStack {
Spacer()
content
Spacer()
}
.background(Color.red)
.padding(10)
.offset(y: showing ? 200 : 0)
}
}
}
Here is possible approach to read content height directly from it during alignment...
struct Test<Content>: View where Content : View {
var content: Content
#State private var showing: Bool = false
#State private var contentHeight: CGFloat = .zero
var body: some View {
VStack {
Button(action: {
withAnimation {
self.showing.toggle()
}
}) {
Text("Toggle")
}
Spacer()
HStack {
Spacer()
content
.alignmentGuide(VerticalAlignment.center) { d in
DispatchQueue.main.async {
self.contentHeight = d.height
}
return d[VerticalAlignment.center]
}
Spacer()
}
.background(Color.red)
.padding(10)
.offset(y: showing ? contentHeight : 0)
}
}
}

SwiftUI: Custom Modal Animation

I made a custom modal using SwiftUI. It works fine, but the animation is wonky.
When played in slow motion, you can see that the ModalContent's background disappears immediately after triggering ModalOverlay's tap action. However, ModalContent's Text views stay visible the entire time.
Can anyone tell me how I can prevent ModalContent's background from prematurely disappearing?
Slow-mo video and code below:
import SwiftUI
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
The solution (thanks to #JWK):
It's probably a bug. It seems that, during the transition animation (when the views are disappearing) the zIndex of the two views involved (the ModalContent and the ModalOverlay) is not respected. The ModalContent (that is supposed to be in front of the ModalOverlay) is actually moved under the ModalOverlay at the beginning of the animation. To fix this we can manually set the zIndex to, for example, 1 on the ModalContent view.
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent()
.transition(.move(edge: .bottom))
.zIndex(1)
}
}.edgesIgnoringSafeArea(.all)
}
}
}
}
The investigation that brings to a solution
Transition animations in SwiftUI have still some issues. I think this is a bug. I'm quite sure because:
1) Have you tried to change the background color of your ModalContent from white to green?
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.green)
}
}
}
}
This way it works (see the following GIF):
2) Another way to make the bug occur is to change the background color of your ContentView to, for example, green, leaving the ModalContent to white:
struct ContentView: View {
#State private var isShowingModal = false
var body: some View {
GeometryReader { geometry in
ZStack {
Button(
action: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = true } },
label: { Text("Show Modal") }
)
ZStack {
if self.isShowingModal {
ModalOverlay(tapAction: { withAnimation(.easeOut(duration: 5)) { self.isShowingModal = false } })
ModalContent().transition(.move(edge: .bottom))
}
}
}
}
.background(Color.green)
.edgesIgnoringSafeArea(.all)
}
}
struct ModalOverlay: View {
var color = Color.black.opacity(0.4)
var tapAction: (() -> Void)? = nil
var body: some View {
color.onTapGesture { self.tapAction?() }
}
}
struct ModalContent: View {
var body: some View {
GeometryReader { geometry in
VStack {
Spacer()
VStack(spacing: 16) {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
.frame(width: geometry.size.width)
.padding(.top, 16)
.padding(.bottom, geometry.safeAreaInsets.bottom)
.background(Color.white)
}
}
}
}
Even in this case it works as expected:
3) But if you change your ModalContent background color to green (so you have both the ContentView and the ModalContent green), the problem occurs again (I won't post another GIF but you can easily try it yourself).
4) Yet another example: if you change the appearance of you iPhone to Dark Appearance (the new feature of iOS 13) your ContentView will automatically become black and, since your ModalView is white, the problem won't occur and everything goes fine.

Resources