How to remove the top safe area in swiftUI - ios

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()

Related

Adding background color for full screen containing List/Tableview in swiftUI

I wanted to change background color of the whole screen(including safe area) in SwiftUI.
Adding following code to outermost view works for all the views except if given view contains List
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
I wanted to add background color for the full screen containing List. I have tried multiple things like,
Adding ZStack with first element as Color.red, but not worked
Color.red with List as an overlay, but it still not worked.
Only thing working for me is converting List to ScrollView, but that I don't want.
Do anyone have any other solution to make that work, or Apple doesn't provide anyway to change background color for List?(should support atleast from iOS14 to latest)
Update: Xcode 14b4 - new beta, new fun.
There is new modifier on Xcode 14 for that (Xcode 14b4):
var body: some View {
List {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
//.scrollContentBackground(Color.blue) // << absent now !!
.scrollContentBackground(.hidden) // Xcode 14b4+
.background(Color.blue) // << also this needed !!
}

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!")
}
}
}

SwiftUI - Using Different Transitions on Nested Views

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
)
}
}

SwiftUI - unexpected behaviour using onTapGesture with mouse/trackpad on iPadOS and Catalyst

I have in my app a layout showing a list of rectangular cards - each one should be tappable (once) to reveal a set of action buttons and more information, etc.
I have implemented this using .onTapGesture() and I have also put .contentShape(Rectangle() to enforce the tappable area. However, while my implementation works fine for touchscreen interface, when I'm using it with the iPadOS mouse support, and on Catalyst for that matter, I see some very unexpected behaviour.
I've made a minimal reproducible example below that you can copy to recreate the problem.
Where the problems are when using mouse/trackpad input:
Not every click of the mouse is recorded as a tap gesture. This is happening mostly arbitrary except from in a few cases:
It seems to click either only in very specific areas, or when clicking multiple times in the same spot.
It seems that in a lot of cases only every other click is recorded. So I double click to get only one tap gesture.
It isn't evident in this example code, but in my main app the tappable areas are seemingly arbitrary - you can usually click near text or in alignment with it to record a tap gesture, but not always.
If you are running the example code you should be able to see the problem by repeatedly moving the mouse and attempting one click. It doesn't work unless you click multiple times in the same spot.
What does work as expected:
All input using touch instead of mouse; regardless of where you tap it records a tap gesture.
Mouse input when running as a native Mac target. The issues mentioned above are only for mouse/trackpad when running the example under iPadOS and Mac Catalyst.
Code I used to recreate this problem (has a counter to count every time a tap gesture is recorded):
struct WidgetCompactTaskItemView: View {
let title: String
let description: String
var body: some View {
HStack {
Rectangle()
.fill(Color.purple)
.frame(maxWidth: 14, maxHeight: .infinity)
VStack(alignment: .leading) {
Text(title).font(.system(size: 14, weight: .bold, design: .rounded))
Text(description).font(.system(.footnote, design: .rounded))
.frame(maxHeight: .infinity)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(1)
.padding(.vertical, 0.1)
Spacer()
}
.padding(.horizontal, 6)
.padding(.top, 12)
}
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .leading)
.background(Color.black)
.cornerRadius(16)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 0.5)
)
}
}
struct ContentView: View {
#State var tapCounter = 0
var body: some View {
VStack {
Text("Button tapped \(tapCounter) times.")
WidgetCompactTaskItemView(title: "Example", description: "Description")
.contentShape(Rectangle())
.onTapGesture(count: 1) {
tapCounter += 1
}
Spacer()
}
}
}
I have tried several things including moving modifiers around, setting eoFill to true on the contentShape modifier (which didn't fix the problem but simply made different unexpected behaviour).
Any help to find a solution that works as expected and works consistently whether mouse or touch would be much appreciated. I am not sure if I am doing something wrong or if there is a bug here, so please try and recreate this example yourself using the code to see if you can reproduce the problem.
So I realised that there was a much better solution that could bypass all the oddities that .onTapGesture had for me with mouse input. It was to encapsulate the whole view in a Button instead.
I made this into a modifier similar to onTapGesture so that it's much more practical.
import Foundation
import SwiftUI
public struct UltraPlainButtonStyle: ButtonStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct Tappable: ViewModifier {
let action: () -> ()
func body(content: Content) -> some View {
Button(action: self.action) {
content
}
.buttonStyle(UltraPlainButtonStyle())
}
}
extension View {
func tappable(do action: #escaping () -> ()) -> some View {
self.modifier(Tappable(action: action))
}
}
Going through this:
I first have a button style which simply returns the label as is. This is necessary because the default PlainButtonStyle() still has a visible effect when clicked.
I then create a modifier that encapsulates the content given in a Button with this button style, then add that as an extension to View.
Usage example
WidgetCompactTaskItemView(title: "Example", description: "Description")
.tappable {
tapCounter += 1
}
This has solved all problems I've been having with clickable area using a mouse.

Resources