Prevent touch passing from Hstack spacing to other views - ios

I am displaying HStack on top of MapView using ZStack. The problem that i am facing is user touches can still respond to MapView from the Spacing between Hstack. How can i prevent this? A trick that i can use is set invisible background color.
.background(Color.black.opacity(0.0001))
import SwiftUI
import MapKit
struct ConfusedView: View {
#State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(
latitude: 51.23,
longitude: -0.1275),
span: MKCoordinateSpan(
latitudeDelta: 0.5,
longitudeDelta: 0.5)
)
var body: some View {
ZStack {
Map(coordinateRegion: $region)
HStack(spacing: 50) {
Rectangle()
.fill(.red)
.frame(width: 100)
Rectangle()
.fill(.red)
.frame(width: 100)
Rectangle()
.fill(.red)
.frame(width: 100)
}
//.background(Color.black.opacity(0.0001))
.border(Color.green, width: 10)
.frame(height: 300)
}
}
}
struct ConfusedView_Previews: PreviewProvider {
static var previews: some View {
ConfusedView()
}
}
I also tried using contentShape but didnot work. Whats the best way to handle this case?
Image

There are 2 ways we can solve this problem:
Color.black.opacity(0.0001) should be used (even on 10-bits-per-channel displays). This produces a colour that is so transparent that it has no effect on your appearance, as well as a tappable area that fills its frame. I'm not sure if SwiftUI is smart enough to skip rendering the colour, so I'm not sure if it affects performance.
Get the frame size with a GeometryReader, then generate the tappable area with a contentShape:
GeometryReader { proxy in
Color.clear.contentShape(Path(CGRect(origin: .zero, size: proxy.size)))
}

Related

onDrag preview isn’t just of the dragged view but also displays the background the view is laid on top of

I’m using an .onDrag modifier on a view that has rounded corners:
struct RootView: View {
#State var dragging: Bool = false
var body: some View {
VStack {
Color.red.cornerRadius(32)
.frame(width: 300, height: 300)
.onDrag {
dragging = true
return NSItemProvider(object: NSString())
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.green)
}
}
The problem is when I invoke a drag - due to the automatic drop shadow effect applied by the system - it doesn’t feel as though I’m dragging the card on its own. See below.
Is there any way to get the card to look dragged with a transparent background, as opposed to above whatever background colour it was laid on top of?
There is content shape type for this,
Color.red.cornerRadius(32)
.frame(width: 300, height: 300)
.contentShape(.dragPreview, RoundedRectangle(cornerRadius: 32)) // << here !!
.onDrag {

SwiftUI, Render text off screen that is inside an HStack

I have three views inside an HStack. The first two are HStack's and the third is text. I want to use .offset to make the Text go off screen. I then have a DragGesture() which allows me to pull the view over.
Although when I pull, the text is not there. I played around with the .offset value and when I lowered it so only part of the text is off screen, the rest of the text is not there.
How can I get the text to render/ actually be viewable once I drag the screen?
import SwiftUI
struct ContentView: View {
#State private var draggedOffset = CGSize.zero
var body: some View {
HStack {
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
Spacer()
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
Spacer()
Text("Hello, world! Testing, Testing")
.padding()
.lineLimit(1)
}
.padding()
.animation(.spring())
.offset(x: draggedOffset.width)
.gesture(DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = CGSize.zero
}
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It's a bit tricky. The main thing here is .fixedSize(horizontal: true, vertical: false) - that lets the Text expand horizontally. There's no need for .lineLimit(1).
You should also remove the Spacer()s. Because those also expand horizontally, SwiftUI will get stuck trying to figure out which ones to expand and which to not.
Then, there's the Color.clear. This has a couple purposes:
Give expected resizing behavior: Color can be freely resized, so if you ever want to use ContentView inside another View, you won't run into trouble.
Align the HStack: Because it's wider than the screen width, SwiftUI will center it by default. You want it aligned left, so you can pass in .leading to the alignment parameter.
struct ContentView: View {
#State private var draggedOffset = CGSize.zero
var body: some View {
Color.clear /// placeholder view that fills the screen width
.background( /// use this to constrain everything to the bounds of the screen
HStack {
Rectangle()
.frame(width: 100, height: 100) /// `alignment` is unnecessary here
Rectangle()
.frame(width: 100, height: 100)
Text("Hello, world! Testing, Testing")
.padding()
.fixedSize(horizontal: true, vertical: false) /// make the Text extend as much as possible horizontally
}, alignment: .leading /// align everything left
)
.padding()
.animation(.spring())
.offset(x: draggedOffset.width)
.gesture(
DragGesture()
.onChanged { value in
self.draggedOffset = value.translation
}
.onEnded { value in
self.draggedOffset = CGSize.zero
}
)
}
}
Result:

decrease the tappable area of a Rectangle SwiftUI

How is it possible to decrease the area where the user can tap on a Rectangle with rounded Corners. When im tapping at the Position of the image, then there will be a reaction although i dont want it.
struct ContentView: View {
#State var x = false
var body: some View {
Rectangle()
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(x ? 10.0 : 35.0).animation(Animation.easeOut(duration: 0.4).delay(0.3))
.foregroundColor(x ? Color.blue : Color.red).animation(Animation.easeIn(duration: 0.1).delay(0))
.onTapGesture {
x = !x
}
}
}

SwiftUI ScrollView BlendMode Issues

I have run into an issue with SwiftUI and ScrollView. When I have a subview with a blend mode attribute in a scrollview, the blend does not affect what is behind the scrollview. But if I put the same item in a Stack, it does affect the background.
I tried to apply the blend mode directly to the scrollview, but since there are other items in the subview that are not blended, it messes that up.
Here is a sample to illustrate the issue. with this, you can see the gradient blend works in the Stack but not in the scrollview.
let mode: BlendMode = .overlay
func item(_ num: Int) -> some View {
return AnyView(
ZStack {
Rectangle()
.fill( LinearGradient(gradient: Gradient(colors: [Color.blue, Color.black]), startPoint: .top, endPoint: .bottom))
.frame(width: 100, height: 100)
.blendMode(mode)
Text("Number = \(num)")
.font(.headline)
.foregroundColor(Color.white)
}
)
}
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
HStack (spacing: 50) {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
ScrollView {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
//.blendMode(mode)
/*
blendMode for item() in ScrollView does not work.
Uncomment blendMode modifier above to get gradient blend, but text is not correct
*/
}
}
}
}
I have also tried to apply the black-to-red Rectangle as a background of the Scrollview, and that doesn't fix it, either.
ScrollView {
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
.background(
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
)
In the pictures below, the look I want is in the left VStack.
No blend mode modifier on ScrollView
Blend mode modifier applied to Scrollview
Any idea how to get around this?
Only thing I could think, which seems very hacky, was to basically create two scrollviews z-stacked with blend applied to only the scrollview needing it and then put the items not blended in the topmost scrollview.
However, I don't think that is viable as I see no way to sync scrolling across the two scrollviews.
I've come across a similar issue and from experimenting it seems as though the blend modes are affected when the SwiftUI View with the blend mode set is directly inside a UIHostingView (such as a ScrollView).
You can get the correct blending by placing another SwiftUI view below the one with the blend mode inside the scroll view, e.g.
ScrollView {
ZStack {
Rectangle()
.fill(RadialGradient(gradient: Gradient(colors: [Color.black, Color.red]), center: .topLeading, startRadius: 200, endRadius: 800))
VStack (spacing: 50) {
ForEach((1...5).reversed(), id: \.self) {
item($0)
}
}
}
}
This doesn't quite fix the issue in your example as the two backgrounds are not quite aligned, but in a case where the scroll view takes up the screen it might help.
It seems that adding a .background to the scroll view adds it below the ScrollView rather than at the bottom of the hierarchy in the ScrollView which is why that doesn't seem to fix it.
My issue was in a List row (which is backed by a UITableView), and setting an explicit background on the item inside the List helped there. Hopefully this will point you/others in the right direction.

Anchor point in SwiftUI

I'm playing with SwiftUI animations. I wanted to make a square bigger modifying its frame with an animation this way:
struct ContentView: View {
let squareWidth = CGFloat(100)
let squareHeight = CGFloat(100)
#State private var bigger = false
var body: some View {
VStack {
Color.green
.frame(width: bigger ? self.squareWidth * 2 : self.squareWidth, height: self.squareHeight)
.animation(.default)
Button(action: {
self.bigger.toggle()
}) {
Text("Click me!")
}
}
}
}
The animation happens from the centre of the View, that is the anchor point (0.5, 0.5). Is there a way to change this anchor point? I found out that I can use scaleEffect on the view specifying an anchor:
struct ContentView: View {
let squareWidth = CGFloat(100)
let squareHeight = CGFloat(100)
#State private var bigger = false
var body: some View {
VStack {
Color.green
.frame(width: self.squareWidth, height: self.squareHeight)
.scaleEffect(x: bigger ? 2 : 1, y: 1, anchor: .leading)
.animation(.default)
Button(action: {
self.bigger.toggle()
}) {
Text("Click me!")
}
}
}
}
But it doesn't seem exactly the same. If I needed to apply several effects I should specify the anchor point for each of them, instead of specifying the anchor point on the view just one time. On UIKit I could write:
var myView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
myView.layer.anchorPoint = CGPoint(x: 0, y: 0.5) //that is, the .leading of SwiftUI
It is possible, but your approach must change.
There is an implied, built-in centring of views in SwiftUI, which can be confusing.
The parent view is the one placing the content in the centre, recalculating the position of its subviews each time their frames change.
Embedding the VStack in a HStack and adding borders to both clearly shows this behaviour.
Also, note the Spacer(), which pushes the view to the left.
HStack {
VStack {
Color.green
.frame(width: bigger ? self.squareWidth * 2 : self.squareWidth)
.frame(height: self.squareHeight)
.animation(.default)
Button("Click me!") { self.bigger.toggle() }
}.border(Color.black)
Spacer()
}.border(Color.red)
Using the .layer.anchorPoint in UIKit should have the same effect as using scaleEffect/offset/rotationEffect in SwiftUI, as it should affect the origin of the underlying geometry/texture which is being rendered by the GPU.
If you have a complex view (composed of multiple subviews) that needs the same scale effect, you can group them using a Group { } and use .scaleEffect on it.

Resources