Rotation3DEffect having undesired effect on Text - swiftui

How is this problem different from other animation questions: I have not seen any articles addressing reversed Text. Please see below:
Here is the code that causes this:
.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 20.0,
perspective: 1.0
)
Here is how I tried to resolve: :
Tried to replace 180º with 360º which of course made the card spin twice (undesired but rendered the text straight)
Tried to use .reversed() as a workaround
Played with all of the params of 3DEffect to no avail
Searched internet for solution, no answer to this specific problem
Edit Also if anyone knows why the text renders like that when going back to arabic view.. that would be great additional assistance.
Edit Adding example code, able to just copy and paste and go
Edit Another edit, forgot I tried to use .reversed() as a workaround
import SwiftUI
struct ContentView: View {
let grid = [GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center)]
var body: some View {
VStack {
LazyVGrid(columns: grid, content: {
ForEach(Example.example, id: \.name) { ex in
AnimatedView(data: ex)
}
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Example {
let name: String
let translation: String
static let example =
[Example(name: "First", translation: "Second"),
Example(name: "Third", translation: "Fourth"),
Example(name: "Fifth", translation: "Sixth"),
Example(name: "Seventh", translation: "Eigth"),
Example(name: "Ninth", translation: "Tenth"),
Example(name: "Eleventh", translation: "Twelth")
]
}
struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
}.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}

struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
.rotation3DEffect( //<- here
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
) }.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}

Related

How to animate color change swiftui

I am trying to animate the color transition between green, yellow, and red. How would I go about this? I have tried using animation() on the Circle view, but this created weird bugs in which the StrokeStyle seems to be ignored and the whole circle fills with color during the transition.
import SwiftUI
import UserNotifications
struct CircleTimer_Previews: PreviewProvider {
static var previews: some View {
Group {
CircleTimer() }
}
}
struct CircleTimer : View {
#State var to: CGFloat = 1;
#State var start = false
#State var count = 15
let defaultTime = 15
#State var time = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View{
ZStack{
Color.black.opacity(0.06).edgesIgnoringSafeArea(.all)
VStack{
ZStack{
Circle()
.trim(from: 0, to: 1)
.stroke(Color.black.opacity(0.09), style: StrokeStyle(lineWidth: 35, lineCap: .round))
.frame(width: 280, height: 280)
Circle()
.trim(from: 0, to: self.to)
.stroke(self.count > 10 ? Color.green : self.count > 5 ? Color.yellow : Color.red, style: StrokeStyle(lineWidth: 35, lineCap: .round))
.frame(width: 280, height: 280)
.rotationEffect(.init(degrees: -90))
.rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
VStack{
Text("\(self.count)")
.font(.system(size: 72))
.fontWeight(.bold)
}
}
TimerButtons(start: $start, to: $to, count: $count)
}
}
.onAppear(perform: {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge,.sound,.alert]) { (_, _) in
}
})
.onReceive(self.time) { (_) in
if self.start{
if self.count > 0 {
self.count -= 1
print(self.count)
withAnimation(.default){
self.to = CGFloat(self.count) / 15
print(self.to)
}
}
else{
self.start.toggle()
}
}
}
}
}
As suggested by #Yrb, you must create three separate views of three different colors.

SwiftUI Circle View animation glitch

import SwiftUI
struct CircularProgressView: View {
#Binding var progress: Float
private let strokeStyle = StrokeStyle(lineWidth: 30.0, lineCap: .round, lineJoin: .round)
private let rotation = Angle(degrees: 270.0)
var body: some View {
ZStack {
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(0.2)
.foregroundColor(Color.black)
.rotationEffect(rotation)
.offset(x: 0, y: 10)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(1)
.foregroundColor(Color.white)
.rotationEffect(rotation)
}
.animation(.linear(), value: progress)
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
OtherView(progress: 0.6)
}
struct OtherView : View {
#State var progress : Float = 0.0
var body: some View {
ZStack {
Color.yellow
VStack {
CircularProgressView(progress: self.$progress)
.padding(50)
Button(action: {
if(progress >= 1) {
progress = 0
} else {
progress += 0.1
}
}) {
Text("try me")
.frame(width: 200, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2)
)
.padding(.bottom, 100)
}
}
}.ignoresSafeArea()
}
}
}
What could be the reason?
It is unclear definitely, probably due to undefined size of shapes as a nature... Anyway, seems using drawing group fixes the issue.
Here is a fixed code. Tested with Xcode 13 / iOS 15.
struct CircularProgressView: View {
#Binding var progress: Float
private let strokeStyle = StrokeStyle(lineWidth: 30.0, lineCap: .round, lineJoin: .round)
private let rotation = Angle(degrees: 270.0)
var body: some View {
ZStack {
Group {
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(0.2)
.foregroundColor(Color.black)
.rotationEffect(rotation)
.offset(x: 0, y: 10)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: strokeStyle)
.opacity(1)
.foregroundColor(Color.white)
.rotationEffect(rotation)
}
.padding() // << compensate offset within own bounds
}
.drawingGroup() // << here !!
.animation(.linear, value: progress)
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
OtherView(progress: 0.6)
}
struct OtherView : View {
#State var progress : Float = 0.0
var body: some View {
ZStack {
Color.yellow
VStack {
CircularProgressView(progress: self.$progress)
.padding()
Button(action: {
if(progress >= 1) {
progress = 0
} else {
progress += 0.1
}
}) {
Text("try me")
.frame(width: 200, height: 50)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.blue, lineWidth: 2)
)
.padding(.bottom, 100)
}
}
}.ignoresSafeArea()
}
}
}

radial gradient shape in swiftui

hey everyone, I designed some stuff in figma and now that Im trying to transfer everything to swiftui I'm having some troubles. Does anyone know how I can make this ellipse shaped gradient in swiftui? I tried the swuiftui inspector but it just gave me a circular shape. Thanks for the help.
iOS15 only :
struct ContentView: View {
var body: some View {
Rectangle()
.fill(
.ellipticalGradient(
colors: [.red, .orange, .yellow],
center: .center,
startRadiusFraction: 0.2,
endRadiusFraction: 0.6
)
)
}
}
or :
struct ContentView2: View {
let gradient = Gradient(stops:
[.init(color: .red, location: 0.2),
.init(color: .orange, location: 0.6),
.init(color: .yellow, location: 0.8)])
var body: some View {
Rectangle()
.fill(
.elliptical(gradient,
center: .center,
startRadiusFraction: 0.1,
endRadiusFraction: 0.6)
)
}
}
You can use RadialGradient by applying scaleEffect modifier to shrink it:
struct ContentView: View {
let gradient = Gradient(
stops: [
.init(color: .green.opacity(0.4), location: 0),
.init(color: .green.opacity(0.5), location: 0.7),
.init(color: .green.opacity(0), location: 1),
]
)
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(
RadialGradient(
gradient: gradient,
center: .center,
startRadius: 0,
endRadius: geometry.size.width / 2
)
)
.scaleEffect(x: 1, y: geometry.size.height / geometry.size.width, anchor: .center)
}
}
}
Since iOS 15 you can get rid of both GeometryReader and scaleEffect using EllipticalGradient, so it may be more performant, or at least less code:
struct ContentView: View {
let gradient = Gradient(
stops: [
.init(color: .green.opacity(0.4), location: 0),
.init(color: .green.opacity(0.5), location: 0.7),
.init(color: .green.opacity(0), location: 1),
]
)
var body: some View {
Rectangle()
.fill(
EllipticalGradient(
gradient: gradient,
center: .center
)
)
}
}
Both variants looks totally the same:

SwiftUI slide transition

struct Flashcard: View {
#State var tangoID = randomNum
#State var refreshToggle = false
#State var showingSheet = false
#State var bookmarked = tangoArray[randomNum].bookmark
#State public var showingNoMoreCardsSheet = false
#State private var showResults: Bool = false
#State private var fullRotation: Bool = false
var body: some View {
let zstack = ZStack {
Frontside(id: $tangoID, sheet: $showingSheet, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 180.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 0 : 1)
Backside(id: $tangoID, sheet: $showingSheet, bookmark: $bookmarked, results: $showResults, rotate: $fullRotation)
.rotation3DEffect(.degrees(self.showResults ? 0.0 : 180.0), axis: (x: 0.0, y: -1.0, z: 0.0))
.rotation3DEffect(.degrees(self.fullRotation ? 360.0 : 0.0), axis: (x: 0.0, y: 1.0, z: 0.0))
.zIndex(self.showResults ? 1 : 0)
}
.actionSheet(isPresented: $showingSheet) {
ActionSheet(title: Text("Finished 終わり"), message: Text("お疲れさま! But feel free to keep going."), buttons: [.default(Text("はい"))]);
}
.onTapGesture {
self.handleFlipViewTap()
}
.navigationBarTitle("Study")
.contextMenu(menuItems: {Button(action: {
tangoArray[randomNum].bookmark.toggle()
database.updateUserData(tango: tangoArray[randomNum])
self.fullRotation.toggle()
}, label: {
VStack{
Image(systemName: tangoArray[randomNum].bookmark ? "bookmark" : "bookmark.fill")
.font(.title)
Text(tangoArray[randomNum].bookmark ? "Remove bookmark" : "Bookmark")
}
})
})
return zstack
}
private func handleFlipViewTap() -> Void
{
withAnimation(.linear(duration: 0.25))
{
self.showResults.toggle()
}
}
}
public struct Frontside: View
{
#Binding public var id: Int
public var body: some View
{
ZStack{
RoundedRectangle(cornerRadius: 8, style: .continuous)
.frame(width: 140, height: 149)
.zIndex(0)
VStack {
Text(tangoArray[self.id].kanji)
.font(.system(size: 24))
.fontWeight(.regular)
.padding(25)
.lineLimit(3)
.zIndex(1)
Spacer()
}
VStack {
Spacer()
HStack {
Button(action: {
incorrect(i: self.id)
checkAttempts()
self.id = nextCard()
}) {
Image(systemName: "xmark")
.font(.headline)
.opacity(0.4)
}
Button(action: {
correct(i: self.id)
checkAttempts()
self.id = nextCard()
}) {
Image(systemName: "circle")
.font(.headline)
.opacity(0.4)
}
}
}
.zIndex(2)
}
}
}
I have a view which is a flashcard. When the user taps on the incorrect button I want the flash card to slide to the left of the screen, and when the user taps on the correct button I want the flash card to transition/slide to the right of the watch screen. How do I do that?
When the user taps on the incorrect button I want the flash card to
slide to the left of the screen, and when the user taps on the correct
button I want the flash card to transition/slide to the right of the
watch screen
I created a minimum viable example to show you a possible solution. Take a look and tell me if I can help you more.
struct ContentView: View {
private let objWidth = CGFloat(100)
private let objHeight = CGFloat(200)
private let screenWidth = UIScreen.main.bounds.size.width;
private let screenHeight = UIScreen.main.bounds.size.height;
#State private var objOffset = CGFloat(50)
var body: some View {
VStack {
Rectangle()
.frame(width: objWidth, height: objHeight)
.background(Color.black)
.position(x: objOffset, y: (screenHeight-objHeight)/2.0)
Button(action: {
withAnimation{
self.move()
}
}) {
Text("TAP")
}
}
}
private func move() {
if objOffset > screenWidth/2.0 {
objOffset = objWidth/2.0
} else {
objOffset = screenWidth-objWidth/2.0
}
}
}

Animating gradient fill in swiftUI

Having rectangle with gradient fill and trying to animate color change.
...
Rectangle()
.fill(LinearGradient(
gradient: .init(stops: [Gradient.Stop(color: myColor, location: 0.0), Gradient.Stop(color: .blue, location: 1.0)]),
startPoint: .init(x: 0.5, y: startPoint),
endPoint: .init(x: 0.5, y: 1-startPoint)
))
.animation(Animation.easeIn(duration: 2))
...
While change of starting points is animated nicely; the color change is not animated at all and just "switches"
Any hints?
So if anyone will be struggling with that my current solution is having two gradients in ZStack and animating opacity of the top one
Example gif
Here is rough example, the code sure can be written more nicely:
///helper extension to get some random Color
extension Color {
static func random()->Color {
let r = Double.random(in: 0 ... 1)
let g = Double.random(in: 0 ... 1)
let b = Double.random(in: 0 ... 1)
return Color(red: r, green: g, blue: b)
}
}
struct AnimatableGradientView: View {
#State private var gradientA: [Color] = [.white, .red]
#State private var gradientB: [Color] = [.white, .blue]
#State private var firstPlane: Bool = true
func setGradient(gradient: [Color]) {
if firstPlane {
gradientB = gradient
}
else {
gradientA = gradient
}
firstPlane = !firstPlane
}
var body: some View {
ZStack {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: self.gradientA), startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1)))
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: self.gradientB), startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1)))
.opacity(self.firstPlane ? 0 : 1)
///this button just demonstrates the solution
Button(action:{
withAnimation(.spring()) {
self.setGradient(gradient: [Color.random(), Color.random()])
}
})
{
Text("Change gradient")
}
}
}
}
Update: *In the end I have explored several ways of animating gradient fills and summarized them here: https://izakpavel.github.io/development/2019/09/30/animating-gradients-swiftui.html *
Wanted to share one more simple option.
Two rectangles with animated solid colors on top of each other.
The top ZStack uses a gradient mask to blend them together.
struct ContentView: View {
#State var switchColor = false
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(switchColor ? Color.purple : Color.green)
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(switchColor ? Color.pink : Color.yellow)
}
.mask(LinearGradient(gradient: Gradient(colors: [.clear,.black]), startPoint: .top, endPoint: .bottom))
}
.padding()
.onTapGesture {
withAnimation(.spring()) {
switchColor.toggle()
}
}
}
}