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
}
}
}
Related
I have a SwiftUI View that is meant to be sort of a "warning" background that flashes on and off. It appears inside another View with a size that changes depending on device orientation. Everything works properly upon the first appearance, but if the device is rotated while the warning view is active it incorporates the frame of the previous orientation into the animation, making it not only flash on and off, but also move in and out of the view its positioned in, as well as change to and from the size it was on the previous orientation. Why is this happening and how can I fix it? Generic code:
struct PulsatingWarningBackground: View {
let color : Color
let speed : Double
#State private var opacity : Double = 1
var body: some View {
GeometryReader {geometry in
self.color
.opacity(self.opacity)
.animation(
Animation
.linear(duration: self.speed)
.repeatForever(autoreverses: true)
)
.onAppear(perform: {self.opacity = 0})
}
}
}
struct PulsatingWarningViewHolder: View {
var body: some View {
GeometryReader {geometry in
ZStack {
Color.white
ZStack {
Color.black
PulsatingWarningBackground(color: .red, speed: 1/4)
}.frame(width: geometry.frame(in: .local).width/5, height: geometry.frame(in: .local).height/10, alignment: .center)
}
}
}
}
You can apply animation only to opacity by using withAnimation(_:_:) inside onAppear(perform:). It works properly as you want.
struct PulsatingWarningBackground: View {
let color : Color
let speed : Double
#State private var opacity : Double = 1
var body: some View {
self.color
.opacity(self.opacity)
.onAppear(perform: {
withAnimation(Animation.linear(duration: self.speed).repeatForever()) {
self.opacity = 0
}
})
}
}
struct PulsatingWarningViewHolder: View {
var body: some View {
GeometryReader {geometry in
ZStack {
Color.white
ZStack {
Color.black
PulsatingWarningBackground(color: .red, speed: 1/4)
}
.frame(width: geometry.frame(in: .local).width/5, height: geometry.frame(in: .local).height/10, alignment: .center)
}
}
}
}
This is how it works right now
I want the magnifying glass to pop up when the user is dragging, and show a zoomed in view of the photo to help the user drag the line to the right spot. I can't get the X and Y coordinates right, so it just goes wherever.
This is the ZStack that contains the magnifying glass...
ZStack{
Image("demo")
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(50)
.frame(width: 500, height: 500, alignment: .center)
.position(x: CGFloat(self.magnifyX), y: CGFloat(self.magnifyY))
}
.frame(width: 100, height: 100, alignment: .center)
.clipShape(Circle())
.overlay(
ZStack{
Circle().fill(Color.black).frame(width: 5, height: 5, alignment: .center)
Circle().stroke(Color.black, lineWidth: 4)
}
)
.position(x: CGFloat(self.magnifyX), y: CGFloat(self.magnifyY-75))
I'd be open to other ways of doing it as well, if this is not the correct way to do it.
Thanks for the help!
Not an answer, but I can't comment yet... Are you able to share your code for magnifyX and magnifyY?
Looking at the screen capture you shared, the magnification seems to be following the line, it's obviously just higher, but it's also the opposite of/mirroring your movements, i.e. as you move left to right, it's moving right to left.
Have a look at the code below, the only thing I am still unable to fix/implement is to prevent the image moving off-screen:
import SwiftUI
struct ContentDetailPhoto : View {
//Properties
#State var scale: CGFloat = 1.0
#State var touchScale: CGFloat = 2
#State var isTouchingScreen = false
#State var isZoomedIn = false
#State var pointTouchedOnScreen: CGPoint = CGPoint.zero
#State var panSize: CGSize = CGSize.zero
#State var fingerState: String = "Finger is not touching the image"
#State var prevSize: CGSize = CGSize.zero
//#State var counter = 0
#State var touching = false
#State var xPos = 0
#State var yPos = 0
//let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var cakeName: String
var body: some View {
ZStack {
Image("\(self.cakeName)")
.grayscale(0.999)
.opacity(0.5)
GeometryReader { reader in
Image("\(self.cakeName)")
.resizable()
.cornerRadius(10)
.offset(x: self.panSize.width, y: self.panSize.height)
.scaleEffect(self.isTouchingScreen ? self.touchScale : 1, anchor: UnitPoint(x: self.pointTouchedOnScreen.x / reader.frame(in: .global).maxX, y: self.pointTouchedOnScreen.y / reader.frame(in: .global).maxY))
.aspectRatio(contentMode: self.isZoomedIn ? .fill : .fit)
.frame(maxWidth: UIScreen.main.bounds.size.width - 10, maxHeight: UIScreen.main.bounds.size.height * 0.7, alignment: .center)
.gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { (value) in
self.fingerState = "Finger is touching the image" // for debug purpose only
self.isZoomedIn = true
self.isTouchingScreen = true
self.pointTouchedOnScreen = value.startLocation
self.scale = self.touchScale
self.panSize = CGSize(width: (value.translation.width * self.touchScale/2), height: (value.translation.height * self.touchScale/2))
}
.onEnded { _ in
self.fingerState = "Finger is not touching the image" // for debug purpose only
self.isZoomedIn = false
self.isTouchingScreen = false
self.panSize = CGSize.zero
self.prevSize = CGSize.zero
})
.cornerRadius(10)
.animation(.easeInOut(duration: 1))
//.offset(x: 0, y: -50)
}.edgesIgnoringSafeArea(.top)
// Add a watermark on top of all the layers
// Image("logo_small")
// .resizable()
// .scaledToFit()
// .opacity(0.05)
// .frame(width: UIScreen.main.bounds.width - 10, height: UIScreen.main.bounds.height - 10, alignment: .center)
// .clipShape(Circle())
}
}
}
#if DEBUG
struct MapView01_Previews: PreviewProvider {
static var previews: some View {
Group {
// iPhone SE
ContentDetailPhoto(cakeName: "cake_0000")
.previewDevice("iPhone SE")
// iPhone X
ContentDetailPhoto(cakeName: "cake_0000")
.previewDevice("iPhone X")
}
}
}
#endif
It looks like you're SwiftUI in an iPhone app, using the iOS simulator. The simulator simulates touches. There is no actual API on the iPhone that supports that because you're finger can't magically turn into a magnifying glass (there is no other pointer input).
macOS and iPadOS is a bit of a different story. On macOS you could use the Hover API to natively change the pointer using NSCursor, or make use of the new UIPointerInteraction on iPadOS.
Imagine a Grid (n x n) squares. Those squares are ZStacks. It contains an optinal piece (In this case a circle). If I offset that piece over another ZStack it gets hidden by the other ZStack.
What I'm trying to do is a chess game. Imagine the Circle() being a piece.
This was my initial attemp:
struct ContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
VStack {
ZStack {
Color.blue
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
}
.frame(width: 150, height: 150)
ZStack {
Color.red
}
.frame(width: 150, height: 150)
}
}
}
Also I tried to add an overlay() instead of using a ZStack. Not sure which is more precise for this case, but unluckily i can't add an "optional" overlay like so:
struct ContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
VStack {
Color.blue
.frame(width: 150, height: 150)
.overlay(
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
)
Color.red
.frame(width: 150, height: 150)
}
}
func tileHasPiece() -> Circle? {
return Circle() // This would consult my model
}
}
But as I said, I don't know how to use tileHasPiece() to add an overlay depending on this.
Just put all board static elements below, and all figure active elements above, as in below modified your code snapshot. In such case everthing will be in one coordinate space. (Of course calculation of coordinates for figures is out of this topic)...
struct FTContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
ZStack {
// put board below
VStack {
Color.blue
.frame(width: 150, height: 150)
Color.red
.frame(width: 150, height: 150)
}
// put figure above
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
} // board coordinate space
}
}
How do I get the effect in a translation, that iOS home screen does when opening an App: It scales the App to fullscreen, starting from the App-icon on the home screen?
In my code, I have the icon-frame (CGRect) with position, width and height and I have the final frame. Is there a way to (probably combine some) transitions to get a scaling from the icon-frame to the final frame?
I get somewhat similar with:
view.transition(AnyTransition.scale(scale: 0, anchor: UnitPoint.trailing))
Which scales from zero to the original size, starting from trailing center position.
It's only close as:
it scales from zero instead of the size of the original icon
it starts from a fixed point (trailing, center) but I'd like to let it start from where the icon is.
Just to be sure: It must be a transition as the view is newly created and dropped. I tried with keeping the view and just change its opacity to show/hide it. With many other problems like not getting the reverse animation, when the view disappears.
Here is a demo of idea how such effect could be done with combined transitions... (positions and sizes are hardcoded for demo simplicity - they can be read with geometry reader, alignment guides or anchor preferences, and actually do not affect the transition usage idea, also animation can be configured)
Demo:
struct TestRisingView: View {
let screen = UIScreen.main.bounds
#State var showingView = false
#State var btFrame: CGRect = .zero
var body: some View {
GeometryReader { g in
ZStack {
Rectangle().fill(Color.clear)
self.activatingButton(frame: CGRect(x: 80, y: 30, width: 60, height: 40))
self.activatingButton(frame: CGRect(x: self.screen.maxX - 80, y: 30, width: 60, height: 40))
self.activatingButton(frame: CGRect(x: self.screen.maxX - 80, y: self.screen.maxY - 60, width: 60, height: 40))
self.activatingButton(frame: CGRect(x: 80, y: self.screen.maxY - 60, width: 60, height: 40))
if self.showingView {
self.topView
.zIndex(1)
.transition(
AnyTransition.scale(scale: 0.12).combined(with:
AnyTransition.offset(x: self.btFrame.origin.x - g.size.width/2.0,
y: self.btFrame.origin.y - g.size.height/2.0))
)
}
}
}
}
func activatingButton(frame: CGRect) -> some View {
Button(action: {
withAnimation {
self.btFrame = frame
self.showingView.toggle()
}
}) {
Text("Tap")
.padding()
.background(Color.yellow)
}
.position(frame.origin)
}
var topView: some View {
Rectangle()
.fill(Color.green)
.frame(width: 300, height: 400)
}
}
The above sample code should give you an idea on scaling an image
struct ImageCustomScaling: View {
// image to be scaled
var scaleImage: Image
// scale ratio
var scaleTo: Double
#State private var start = false
var body: some View {
VStack {
scaleImage
.font(.title)
.scaleEffect(self.start ? CGFloat(scaleTo) : 1)
.opacity(self.start ? 1 : 0)
.animation(Animation.interpolatingSpring(stiffness: 25, damping: 5, initialVelocity: 10).delay(0.9))
}
.foregroundColor(.blue)
.onAppear {
self.start = true
}
}
}
Above can be called by something like below
ImageCustomScaling(scaleImage: Image(systemName: "cloud.fill"), scaleTo: 5 )
Added animation modifier to give visual cue. You can change it to meet your needs.
Now in 2021 we finally got an answer from Apple. matchedGeometryEffect solves this problem elegantly and easily.
Here is a little screenshot of what it does:
And here is the code for this demo:
struct Stackoverflow: View {
#Namespace var namespace
#State private var shown: Bool = false
var body: some View {
VStack {
Button {
shown.toggle()
} label: {
Text("Tap Me!\nI'm a red square.")
.multilineTextAlignment(.center)
}
.background(
Color.red
.matchedGeometryEffect(id: "1", in: namespace, properties: .frame))
Spacer()
if shown {
Color.clear
.aspectRatio(contentMode: .fit)
.matchedGeometryEffect(id: "1", in: namespace, properties: .size)
.border(Color.gray)
}
Spacer()
}
.animation(.easeInOut(duration: 1))
}
}
struct Stackoverflow_Previews: PreviewProvider {
static var previews: some View {
Stackoverflow()
}
}
Check here on swift-lab.com, for more information and explanation on how to use matchedGeometryEffect.
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.