SwiftUI - Using Different Transitions on Nested Views - ios

How can I have an overlaying view in SwiftUI with a different transition on different portions? E.g. in my concrete example, I want to achieve the following:
one subview of the overlay view slides in from the bottom
another subview of the overlay view has no transition at all (appears instantly)
the background of the overlay does not slide in from the bottom but fades in
I've already tried all sorts of things and it seems, that I have to set animation and transition on the view that wraps the if showOverlay condition. However, it looks like all the .transition() and .animation() modifiers set on the sub-views of the overlay are ignored then.
Is it actually possible?
The following code is what I thought should somewhat work but apparently doesn't at all:
// any observable external variable, that enables / disables the overlay
#State showOverlay: bool = false
// surrounding view
VStack { /* .. */ }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.overlay(
Group {
if showOverlay {
Overlay()
}
}
)
// overlay view
struct Overlay: View {
var body: some View {
VStack {
// style 1
Text("OverlayContent that slides in from bottom")
.transition(.move(edge: .bottom).combined(with: .opacity))
.animation(.easeInOut(duration: 0.28))
// style 2
Text("OverlayContent that appears instant")
.animation(nil)
}
.background( // style 3
Color.black.opacity(0.2)
.ignoresSafeArea()
.transition(.opacity) // <-- background should not slide up but fade in
)
}
}

Related

ScrollView touch works despite all other touch is disabled

So my goal is to disable touch events for everything on screen and I don't want to use .disable on every view or to use Color.black.opacity(0.00001) because of the code smell. I want it to be a block that isn't visible for the user like if I would overlay Color.clear over the whole view. And I want it to behave like if I were to use Color.black.opacity(0.1) with it disabling touch events on every view underneath.
If I for example use a ZStack with:
Color.black.opacity(0.2) every view underneath will no longer register touch events. (I want this, but it should be transparent)
Color.black.opacity(0) every view underneath will register touch events.
Color.black.opacity(0).contentShape(Rectangle()), some events will register, some won't, for example buttons won't work though scrolling in a ScrollView, or using a toggle will still work.
Here is some example code
struct ContentView: View {
#State var numberOfRows: Int = 10
var body: some View {
ZStack {
Color.white
ScrollView {
VStack {
ForEach(0..<numberOfRows, id: \.self) { (call: Int) in
Text(String(call))
.frame(maxWidth: .infinity)
}
}
}
Button(action: {
numberOfRows += 1
}) {
Color.blue
.frame(width: 300, height: 300)
}
Color.black.opacity(0) // <- change this to 0.x to disable all touch
.contentShape(Rectangle()) // <- Remove this line to make blue button work (opacity needs to be 0)
}
}
}
Why is scrollview still receiving touch events and why is buttons not?
Is there a way to make my touch events for every view underneath, disabled?
Use instead (tested with Xcode 13.4 / iOS 15.5)
Color.clear
// .ignoresSafeArea(.all) // << if need all screen space
.contentShape(Rectangle())
.highPriorityGesture(DragGesture(minimumDistance: 0))

Ignore Safe Area in Xcode with SwiftUI

With the following code, the safe area is ignored even though no (.edgesIgnoringSafeArea(.all)) is set.
Why? How I can reset it or is it a bug?
enter image description here
struct SafeAreaBootcamp: View {
var body: some View {
Text("Hello, World!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
}
}
struct SafeAreaBootcamp_Previews: PreviewProvider {
static var previews: some View {
SafeAreaBootcamp()
}
}
background ignores safe area insets by default – so although the frame or your text view is inset from the top and bottom of an iPhone view, the background will automatically extend into it.
You could elect to use a ZStack to manually layer your view on top of a view that would act as its background. That view wouldn't automatically extend into the safe area, so it'd get you the effect you want.
However, it's possible (and probably preferable) to stick with the background modifier, and tell it not to ignore safe edges. From the documentation:
By default, the background ignores safe area insets on all edges, but you can provide a specific set of edges to ignore, or an empty set to respect safe area insets on all edges:
Rectangle()
.background(
.regularMaterial,
ignoresSafeAreaEdges: []) // Ignore no safe area insets.
Note that this constructor only works with a subset of backgrounds – colors, materials, etc. – that conform to the ShapeStyle protocol. That's explained in the doc page but it's worth repeating.
I would use a zStack, one of the issues with your current code is using the frame(maxHeight : .infinity) you're forcing the view to extend vertically for the entire screen. Below code is a cleaner way to write what you're after.
struct ContentView: View {
var body: some View {
ZStack {
Color.red
Text("Hello, World!")
}
}
}

Change background color of View inside TabView having NavigationView and ScrollView in SwiftUI

I want to build a TabView with 4 tabs having collection views in it. Below is my code of one tab named 'Gallery'.
var body: some View {
NavigationView {
ScrollView {
GridStack(rows: 3, columns: 2) { row, column, totalColumn in
CardView(card: self.cards[(row * totalColumn) + column])
}.padding().background(Color.red)
}
.navigationBarTitle("Gallery")
}
}
When I give background color for ScrollView, scrolling is not working for NavigationView largeTitle. How can I achieve this, I want to give red color for full view's background? What if I need to achieve this same backgorund color for all tabs?
Here is possible approach (scroll view is not broken in such case)
NavigationView {
GeometryReader { gp in
ScrollView {
ZStack(alignment: .top) {
Rectangle().fill(Color.red) // << background
// ... your content here, internal alignment might be needed
}.frame(minHeight: gp.size.height)
}
.navigationBarTitle("Gallery")
}
}

SwiftUI: How can I restrict the tappable area of a view when presenting a modal(actually not modal) view over a main view?

I am developing an app based on a Tabview with three TabItems. Each TabItem is a List and I would be able to show a kind of modal view over those Lists. The problem becomes when I can not call a Sheet as modal view because Sheets are almost full windowed. I need some kind of bottom modal view, so I create a View that I present over a List with higher ZIndex. It seems to work until you click in the tabbar and select another TabItem having deployed the "modal" view. The error is:
[TableView] Warning once only: UITableView was told to layout its
visible cells and other contents without being in the view hierarchy
(the table view or one of its superviews has not been added to a
window). This may cause bugs by forcing views inside the table view to
load and perform layout without accurate information (e.g. table view
bounds, trait collection, layout margins, safe area insets, etc), and
will also cause unnecessary performance overhead due to extra layout
passes.
So, I would like as solution to restrict the tappable area to the "modal" view area. ¿Is there a way to achieve this?
Probably you have some condition state depending on which you present your "modal-like" view, so depending on the same condition you can disable below TabView, like below
TabView {
// ... tabs content here
}.disabled(showingModal)
Update: Here is a demo of approach that I meant (tested with Xcode 11.3+)
struct TestTabViewModal: View {
#State private var selectedTab = 0
#State private var modalShown = false
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
VStack {
Button("Show Modal") { self.modalShown = true }
.padding(.top, 40)
Spacer()
}
.tabItem {
Image(systemName: "1.circle")
}.tag(0)
Text("2").tabItem {
Image(systemName: "1.circle")
}.tag(1)
}.disabled(modalShown)
if modalShown {
RoundedRectangle(cornerRadius: 10)
.fill(Color.yellow)
.frame(width: 320, height: 240)
.overlay(Button("CloseMe") { self.modalShown = false })
}
}
}
}

How to remove the top safe area in swiftUI

I am developing a screen using SwiftUI, want to display my view in the top safe area of the screen, is there any method to achieve this in SwiftUI?
Use this modifier:
.edgesIgnoringSafeArea(.top)
If you want to ignore all the safe area insets you can pass .all
SwiftUI 2.0
You can use this modifier to pass in the region and the edges you need to ignore
.ignoresSafeArea(.container, edges: .top)
Note: Both parameters are optional
This modifier is very useful when you need to react to the keyboard as the region. The old modifier is not deprecated yet and you can still use it as the primary method:
SwiftUI 1.0
Use this modifier if need to support iOS 13
.edgesIgnoringSafeArea(.top)
Note:
You can pass .all for both modifiers if you wish to ignore all edges.
By default SwiftUI views will mostly stay inside the safe area. It will go to the bottom of the screen, but it won’t go near any notch at the top of the device. If you want your view to be truly full screen, then you should use the edgesIgnoringSafeArea() modifier.
struct MyView : View {
var body: some View {
Text("Welcome to Swift UI")
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.top)
}
}
/**
This extension is needed because of deprecating edgesignoringsafearea for iOS 13.0–15.2
https://developer.apple.com/documentation/swiftui/menu/edgesignoringsafearea(_:)
*/
public extension View {
#ViewBuilder
func expandViewOutOfSafeArea(_ edges: Edge.Set = .all) -> some View {
if #available(iOS 14, *) {
self.ignoresSafeArea(edges: edges)
} else {
self.edgesIgnoringSafeArea(edges) // deprecated for iOS 13.0–15.2, look upper
}
}
}
How to use:
MyView()
.expandViewOutOfSafeArea()

Resources