Is it possible to flip a SwiftUI View vertically? - swiftui

Looks like you can rotate a view upside down with UIView, but I can't find anything saying it's possible to do the same thing with a SwiftUI View.
Any help will be appreciated :)

Actually approach is the same to referenced post
Text("Test").font(.largeTitle)
.scaleEffect(CGSize(width: 1.0, height: -1.0)) // << here !!

Here is a convenience extension:
extension View {
func flipped(_ axis: Axis = .horizontal, anchor: UnitPoint = .center) -> some View {
switch axis {
case .horizontal:
return scaleEffect(CGSize(width: -1, height: 1), anchor: anchor)
case .vertical:
return scaleEffect(CGSize(width: 1, height: -1), anchor: anchor)
}
}
}
Use it:
Text("Flip me")
.flipped(.vertical)

Rotate Image As Mirror image by SwiftUI:
struct Rotate3DView: View {
#State var imagename: String = "exImage"
var body: some View {
VStack {
ZStack{
Image(imagename).resizable().opacity(0.3)
.rotation3DEffect(.degrees(180), axis: (x: -10, y: 0, z: 0))
.rotationEffect(.radians(.pi))
.padding(.top,60)
.cornerRadius(5)
.frame(width: 180, height: 280)
Image(imagename).resizable().padding(.bottom,45).frame(width: 180, height: 280)
}
}
}
}
This is very easy for native Users.

Turns out I can just apply this to the surrounding Stack:
.rotationEffect(.degrees(-180))
To flip it vertically

SwiftUI’s rotationEffect() modifier lets us rotate views freely, using either degrees or radians.
For example, if you wanted to rotate some text by -90 degrees so that it reads upwards, you would use this:
Text("Up we go")
.rotationEffect(.degrees(-90))
If you prefer using radians, just pass in .radians() as your parameter, like this:
Text("Up we go")
.rotationEffect(.radians(.pi))
View rotation is so fast that it’s effectively free, so you could even make it interactive using a slider if you wanted:
struct ContentView: View {
#State private var rotation = 0.0
var body: some View {
VStack {
Slider(value: $rotation, in: 0...360)
Text("Up we go")
.rotationEffect(.degrees(rotation))
}
}
}
enter link description here
By default views rotate around their center, but if you want to pin the rotation from a particular point you can add an extra parameter for that. For example if you wanted to make the slider above pivoting the rotation around the view’s top-left corner you’d write this:
struct ContentView: View {
#State private var rotation = 0.0
var body: some View {
VStack {
Slider(value: $rotation, in: 0...360)
Text("Up we go")
.rotationEffect(.degrees(rotation), anchor: .topLeading)
}
}
}
enter link description here

Related

Dynamically set frame dimensions in a view extension

I've been playing around with giving views a gradient shadow (taken from here and here) and while these achieve most of what I need, they seem to have a flaw: the extension requires you to set a .frame height, otherwise the gradient looks really desaturated (as it's taking up the entire height of the device screen). It's a little hard to describe, so here's the code:
struct RainbowShadowCard: View {
#State private var cardGeometryHeight: CGFloat = 0.0
#State private var cardGeometryWidth: CGFloat = 0.0
var body: some View {
VStack {
Text("This is a card, it's pretty nice. It has a couple of lines of text inside it. Here are some more lines to see how it scales.")
.font(.system(.body, design: .rounded).weight(.medium))
}
.frame(maxWidth: .infinity)
.padding()
.foregroundColor(.white)
.background {
GeometryReader { geo in
Color.black
.onAppear {
cardGeometryHeight = geo.size.height
cardGeometryWidth = geo.size.width
print("H: \(cardGeometryHeight), W: \(cardGeometryWidth)")
}
}
}
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.padding()
.multicolorGlow(cardHeight: cardGeometryHeight, cardWidth: cardGeometryWidth)
}
}
extension View {
func multicolorGlow(cardHeight: CGFloat, cardWidth: CGFloat) -> some View {
ZStack {
ForEach(0..<2) { i in
Rectangle()
.fill(
LinearGradient(colors: [
.red,
.green
], startPoint: .topLeading, endPoint: .bottomTrailing)
)
// The height of the frame dictates the saturation of
// the linear gradient. Without it, the gradient takes
// up the full width and height of the screen, resulting in
// a washed out / desaturated gradient around the card.
.frame(height: 300)
// My attempt at making the height and width of this view
// be based off the parent view
/*
.frame(width: cardWidth, height: cardHeight)
*/
.mask(self.blur(radius: 10))
.overlay(self.blur(radius: 0))
}
}
}
}
struct RainbowShadowCard_Previews: PreviewProvider {
static var previews: some View {
RainbowShadowCard()
}
}
I've managed to successfully store the VStack height and width in cardGeometryHeight and cardGeometryWidth states respectfully, but I can't figure out how to correctly pass that into the extension.
In the extension, if I uncomment:
.frame(width: cardWidth, height: cardHeight)
The VStack goes to a square of 32x32.
Edit
For the sake of clarity, the above solution "works" if you don't use a frame height value for the extension, but it doesn't work very nicely. Compare the saturation of the shadow in this image to the original, and you'll see a big difference between a non framed approach and a framed approach. The reason for this muddier gradient is the extension is using the screen bounds for the linear gradient, so our shadow gradient isn't getting the benefit of the "start" and "end" saturation of the red and green, but the middle blending of the two.

SwiftUI DragGesture() get start location on screen

I want to make a view "swipeable", however this view only covers a small share of the whole screen. Doing:
var body: some View {
ZStack {
Text("ABC")
.gesture(DragGesture().onChanged(onChanged))
}
}
func onChanged(value: DragGesture.Value) {
print(value.startLocation)
print("onChanged", value.location)
}
Gives me relative values where the user has swiped - however, without knowing the exact location of the view, I'm unable to know where the user's touch is on the screen.
In other words, when the Text is in the center of the screen, going all the way to the right edge gives 200px. But let's say the Text moved to the left, then it would be almost 400. I tried putting another gesture on the ZStack to save the initial value, however it appears you can't have multiple gestures within each other.
How can I make the listener translate the values so the left edge is 0 and the right edge is displayWidth in px, regardless of where the Text is?
You can make use of coordinate spaces, and use that with DragGesture.
Code:
struct ContentView: View {
#State private var location: CGPoint = .zero
var body: some View {
VStack(spacing: 30) {
Text("ABC")
.background(Color.red)
.gesture(
DragGesture(coordinateSpace: .named("screen")).onChanged(onChanged)
)
Text("Location: (\(location.x), \(location.y))")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.coordinateSpace(name: "screen")
}
private func onChanged(value: DragGesture.Value) {
location = value.location
}
}

SwiftUI View Jumps on Initial Drag

I am looking to have a process where a card starts at the bottom of the screen and can be dragged up and down (vertical axis only). I have an issue where on initial drag the card jumps from the bottom of the screen to the top. I have simplified the below to its most minimum to demonstrate the issue. I was thinking it was some sort of coordinate space issue, but changing between local and global doesn't help.
struct ContentView: View {
#State private var offset = CGSize(width: 0, height: UIScreen.main.bounds.height * 0.9)
var body: some View {
GeometryReader { geo in
Color(.red).edgesIgnoringSafeArea(.all)
Color(.orange)
.clipShape(RoundedRectangle(cornerRadius: 20))
.offset(offset)
.animation(.spring())
.gesture(DragGesture()
.onChanged { gesture in
offset.height = gesture.translation.height
}
)
}
}
}
Again when the finger is first place at the top of the bottom orange view and the drag gesture begins upwards, the orange view jumps to the top of the red view.
You can separate the two different offsets to two different modifiers. First add .offset() with your starting amount. Then add another .offset() that will offset the view from the starting offset.
I also added an .onEnded gesture so that it doesn't lag when starting to drag a 2nd time. You'll probably want to change the .onEnded so that at the end of the gesture the view animates toward a locked position.
struct DragView: View {
#State private var offsetY: CGFloat = 0
#State private var lastOffsetY: CGFloat = 0
var body: some View {
GeometryReader { geo in
Color(.red)
.ignoresSafeArea()
Color(.orange)
.clipShape(RoundedRectangle(cornerRadius: 20))
.offset(y: UIScreen.main.bounds.height * 0.8)
.offset(y: offsetY)
.animation(.spring())
.gesture(DragGesture()
.onChanged { gesture in
withAnimation(.spring()) {
offsetY = lastOffsetY + gesture.translation.height
}
}
.onEnded { _ in
lastOffsetY = offsetY
}
)
}
}
}

Grouping Shapes in SwiftUI

I have a face shape drawn in swiftUI. The face is a circle and eyes are two circles . I want to group these three Circles as one Single shape so that when I move face shape the eyes also should move with the face also I want to draw the eyes based on relative face circle coordinate and not the coordinate system of rect of draw func.
Something like this
struct ContentView: View {
#State var x: CGFloat = 100
#State var y: CGFloat = 100
var body: some View {
VStack{
//Shape Group
Group{
//Put your Shape here
ZStack{
Rectangle().fill(Color.green)
Circle().fill(Color.blue)
}.frame(width: 100, height: 100, alignment: .center )
}
.position(x: self.x, y: self.y)
//Slider for position
HStack{
Text("x: \(x)")
Slider(value: $x, in: 0...400, step: 1).padding(.horizontal, 10)
}
HStack{
Text("y: \(y)")
Slider(value: $y, in: 0...500, step: 1).padding(.horizontal, 10)
}
}
}
}

SwiftUI - animating a new image inside the current view

I have a View where I use a Picture(image) subview to display an image, which can come in different height and width formats.
The reference to the image is extracted from an array, which allows me to display different images in my View, by varying the reference. SwiftUI rearrange the content of view for each new image
I would like an animation on this image, say a scale effect, when the image is displayed
1) I need a first .animation(nil) to avoid animating the former image (otherwise I have an ugly fade out and aspect ratio deformation). Seems the good fix
2) But then I have a problem with the scaleEffect modifier (even if I put it to scale = 1, where it should do nothing)
The animation moves from image 1 to image 2 by imposing that the top left corner of image 2 starts from the position of top left corner of image 1, which, with different widths and heights, provokes a unwanted translation of the image center
This is reproduced in the code below where for demo purposes I'm using system images (which are not prone to bug 1))
How can I avoid that ?
3) In the demo code below, I trigger the new image with a button, which allows me to use an action and handle "scale" modification and achieve explicitly the desired effect. However in my real code, the image modification is triggered by another change in another view.
Swift knows that, hence I can use an implicit .animation modifier.
However, I can't figure out how to impose a reset of "scale" for any new image and perform my desired effect.
If I use onAppear(my code), it only works for the first image displayed, and not the following ones.
In the real code, I have a Picture(image) view, and Picture(image.animation()) does not compile.
Any idea how to achieve the action in the below code in the Button on an implicit animation ?
Thanks
import SwiftUI
let portrait = Image(systemName: "square.fill")
let landscape = Image(systemName: "square.fill")
struct ContentView: View {
#State var modified = false
#State var scale: CGFloat = 1
var body: some View {
return VStack(alignment: .center) {
Pictureclip(bool: $modified)
.animation(nil)
.scaleEffect(scale)
.animation(.easeInOut(duration: 1))
Button(action: {
self.modified.toggle()
self.scale = 1.1
DispatchQueue.main.asyncAfter(deadline: .now() + 1)
{self.scale = 1}
}) {
Text("Tap here")
.animation(.linear)
}
}
}
}
struct Pictureclip: View {
#Binding var bool: Bool
var body: some View {
if bool == true {
return portrait
.resizable()
.frame(width: 100, height: 150)
.foregroundColor(.green)
} else {
return landscape
.resizable()
.frame(width: 150, height: 100)
.foregroundColor(.red)
}
}
}
I have a semi answer to my question, namely points 1 & 2 (here with reference to two jpeg images in the asset catalog)
import SwiftUI
let portrait = Image("head")
let landscape = Image("sea")
struct ContentView: View {
#State var modified = false
#State var scale: CGFloat = 0.95
var body: some View {
VStack(){
GeometryReader { geo in
VStack {
Picture(bool: self.modified)
.frame(width: geo.size.width * self.scale)
}
}
Spacer()
Button(action: {
self.scale = 0.95
self.modified.toggle()
withAnimation(.easeInOut(duration: 0.5)){
self.scale = 1
}
}) {
Text("Tap here")
}
}
}
}
struct Picture: View {
var bool: Bool
var body: some View {
if bool == true {
return portrait
.resizable().aspectRatio(contentMode: .fit)
.padding(.all,6.0)
} else {
return landscape
.resizable().aspectRatio(contentMode: .fit)
.padding(.all,6.0)
}
}
}
This solution enables scaling without distorting the aspect ratio of the new image during the animation. But It does not work in a code where the image update is triggered in another view. I guess I have to restructure my code, either to solve my problem or to expose it more clearly.
Edit: a quick and dirty solution is to put the triggering code (here the action code in the button) in the other view. Namely, put in view B the code that animates view A, with a state variable passed to it (here, "scale"). I'm sure there are cleaner ways, but at least this works.
I am not sure about it, but maybe it can be helpful for you.
Use DataBinding structure. I use it like this:
let binding = Binding<String>(get: {
self.storage
}, set: { newValue in
self.textOfPrimeNumber = ""
self.storage = newValue
let _ = primeFactorization(n: Int(self.storage)!, k: 2, changeable: &self.textOfPrimeNumber)
})