SwiftUI MagnificationGesture() delay - swiftui

I have a standart app with a view, which you can scale in/out by pinch gesture.
It's work, but I have a little delay at first, it look like jumpy zoom. Does anybody know a solution to make it work smoother ?
Example of code here:
VStack {
Image("image")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 200)
.scaleEffect(scale)
.gesture(MagnificationGesture()
.updating($scale, body: { (value, scale, trans) in
scale = value.magnitude
})
)
}

In order to solve the "jumpy zoom" you only need to add an animation.
Maybe something like this for example :
Image("image")
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 200)
.scaleEffect(scale)
.gesture(MagnificationGesture()
.updating($scale, body: { (value, scale, trans) in
scale = value.magnitude
})
)
.animation(Animation.easeInOut(duration: 2.0), value: scale)// Animation to solve the "jumpy zoom"

Related

SwiftUI: How can I align a view to a background image

I have been trying to align a view to a background image, but I haven't been able to find a solution that works for all devices. I am targeting iPhones in landscape orientation.
In this example I want to make the red rectangle align with the iMac screen. This code gets pretty close, by using an offset. It looks good in the preview canvas, but doesn't align in the Simulator or on a device.
I tried using .position(x:y:), but that was even more messy.
I found that if I crop the background so the target region is exactly centered, then it is possible, but I really hope that's not the only solution.
struct GeometryView: View {
let backgroundImageSize = CGSize(width: 1500, height: 694)
let frameSize = CGSize(width: 535, height: 304)
var body: some View {
GeometryReader { geometry in
let widthScale = geometry.size.width / backgroundImageSize.width
let heightScale = geometry.size.height / backgroundImageSize.height
let scale = widthScale > heightScale ? widthScale : heightScale
let frame = CGSize(width: frameSize.width * scale,
height: frameSize.height * scale)
ZStack {
Rectangle()
.frame(width: frame.width, height: frame.height)
.foregroundColor(.red).opacity(0.5)
.offset(x: 5, y: -8)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(
Image("imac-on-desk")
.resizable()
.scaledToFill()
.ignoresSafeArea())
}
}
}
background image
this would work, but only on an iPhone 12. If you use .scaledToFill on the image the different display aspect ratios of phones will lead to different offsets. You could at least crop the background image , so the white screen is exactly in the center of the image.
var body: some View {
GeometryReader { geometry in
ZStack {
Image("background")
.resizable()
.scaledToFill()
.ignoresSafeArea()
Rectangle()
.foregroundColor(.red).opacity(0.5)
.frame(width: geometry.size.width / 2.45,
height: geometry.size.height / 2.1)
.offset(x: -geometry.size.width * 0.025, y: 0)
}
}
}

SwiftUI squared image in a circle

I try to draw a squared Image inside of a Circle to get something like that (without blue square here. It is just to show image squared border):
This code makes squared image over all circle.
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
.background(.orange)
}
This code makes a small image on the circle or no circle at all:
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.foregroundColor(.blue)
.background(.orange)
}
I try to find a solution to have squared image inside the circle. It should not goes outside of it.
Maybe, I would also need a small margin between image and circle's border.
Is there a way to do that easily? Or I have to use math to get circle border or something like that?
Asperi just beat me to it, but this view will just take an icon name and a radius and return a squared icon perfectly in a circle:
struct ImageOnCircle: View {
let icon: String
let radius: CGFloat
var squareSide: CGFloat {
2.0.squareRoot() * radius
}
var body: some View {
ZStack {
Circle()
.fill(.orange)
.frame(width: radius * 2, height: radius * 2)
Image(systemName: icon)
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.frame(width: squareSide, height: squareSide)
.foregroundColor(.blue)
}
}
}
Use:
ImageOnCircle(icon: "trash", radius: 150)
Here is a demo of possible approach - use overlay + geometry reader to calculate internal rectangle where image is injected.
Tested with Xcode 13 / iOS 15 (blue rect is of Preview one for selected image)
Circle()
.fill(.orange)
.overlay(GeometryReader {
let side = sqrt($0.size.width * $0.size.width / 2)
VStack {
Rectangle().foregroundColor(.clear)
.frame(width: side, height: side)
.overlay(
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
})
.frame(width: 100, height: 100)

How to soften edges of a Rectangle SwiftUI

I'm currently running into a problem of "harsh" edges when I try to create a rounded rectangle using a gradient.
In this gradient you can see around the corners it gets especially dark, and I'm not sure how to fix this.
I'm guessing it's stemming from using an extension to create the gradient so that I can use it as the foreground color instead of the background, but I'm not sure what I would use instead to fix this problem.
var body: some View {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 20)
.gradientForeground(colors: [Color("MainColor1"), Color("MainColor2")])
.frame(width: geometry.size.width * 0.9, height: 100, alignment: .center)
.padding(.leading, geometry.size.width * 0.05)
}
}
}
extension View {
public func gradientForeground(colors: [Color]) -> some View {
self.overlay(LinearGradient(gradient: .init(colors: colors),
startPoint: .topLeading,
endPoint: .bottomTrailing))
.mask(self)
}
}
Since you are working on Shape because you used RoundedRectangle, the right and correct way of coloring is fill, your issue is not connected to View to make an extension but it is about Shape.
extension Shape {
public func gradientForeground(colors: [Color]) -> some View {
self.fill(LinearGradient(gradient: .init(colors: colors), startPoint: .topLeading, endPoint: .bottomTrailing))
}
}
Use case:
struct ContentView: View {
var body: some View {
RoundedRectangle(cornerRadius: 50)
.gradientForeground(colors: [Color.red, Color.yellow])
.frame(width: 300, height: 300, alignment: Alignment.center)
}
}

How do I create a looping heartbeat animation in swiftUI?

I am trying to create an animation where a small heart icon is pumping. I have the two images I believe are sufficient to create the effect, but I have no idea how to create this animation effect. I have tried several things and none of them seem to work.
Any help you can offer will be greatly appreciated.
Here's what I have so far:
#State var show : Bool = false
var body: some View {
VStack(alignment: .trailing){
ZStack{
BlackView()
if(show){
Image("heartOrgan1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height:50)
.hidden()
Image("heartOrgan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
} else {
Image("heartOrgan1")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
Image("heartOrgan")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50, height: 50)
.hidden()
}
}
.onAppear(){
withAnimation { self.show.toggle()}
}
}
}
The general idea is to loop the switching between two heart images that represent a heart beating. I am interested in using these particular heart images as they look like actual hearts, and I like that.
You don't necessarily need two images for this. You can use one image and apply the scale effect on it. Make the one image scale up and add a delay to it. Then make it repeat.
Example:
#State private var animationAmount: CGFloat = 1
var body: some View {
ZStack {
Color.black
Image(systemName: "heart.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.red)
.scaleEffect(animationAmount)
.animation(
.linear(duration: 0.1)
.delay(0.2)
.repeatForever(autoreverses: true),
value: animationAmount)
.onAppear {
animationAmount = 1.2
}
}
}
You can also change the decimal value in inside the delay() to have different heartbeats. The heartbeat looks consistent with delays between 0.1 - 0.4.
I would slightly modify the great answer provided by Jame to make the animation a little bit more realistic, just by changing the linear animation for a spring animation like this:
struct ContentView: View {
#State private var animationAmount: CGFloat = 1
var body: some View {
ZStack {
Color.black
Image(systemName: "heart.fill")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.red)
.scaleEffect(animationAmount)
.animation(
.spring(response: 0.2, dampingFraction: 0.3, blendDuration: 0.8) // Change this line
.delay(0.2)
.repeatForever(autoreverses: true),
value: animationAmount)
.onAppear {
animationAmount = 1.2
}
}
}
}
You can play with repetition, dampingFraction and blenDuration parameters to get a more customised animation.
:)

pinch to zoom graph chart in the chart frame only SwiftUI

I'd like to make my custom linear graph chart zoomable(?). after research I used pinch to zoom( MagnificationGesture in swiftUI) to make it. now my graph can zoom out or in. but when I zoom out it cover the whole scene which is I never want. I want my graph only zoom out in the chart frame(I mean limited area of screen) only. How can I solve this problem?
#State var scale : CGFloat = 1.0
let frame = CGSize(width: 350, height: 500)
public init(data: [Int], style: ChartStyle = Styles.lineChartStyleOne ){
self.style = style
}
var body: some View {
ZStack(alignment: .center){
VStack(alignment: .leading){
Spacer()
GeometryReader{ geometry in
Line(data: self.data, frame: .constant(geometry.frame(in: .local)), touchLocation: self.$touchLocation, showIndicator: self.$showIndicatorDot)
.offset(x: 15,y:0)
Legend(data: self.data, frame: .constant(geometry.frame(in: .local)), hideHorizontalLines: .constant(false))
RangeLineView(data: self.data, frame: .constant(geometry.frame(in: .local)))
RangeView(data: self.data, frame: .constant(geometry.frame(in: .local)))
}
.frame(width: frame.width, height: frame.height)
.scaleEffect(scale)
.gesture(MagnificationGesture()
.onChanged { value in
self.scale = value.magnitude
}
)
.offset(x: 0, y: 0)
}.frame(width: self.style.chartFormSize.width, height: self.style.chartFormSize.height)
}
}
figure it out my self...
.clipped(Rectangle())
simple but it will do that magic.