How can I make moving/live dash line in SwiftUI? - swift

I have this Rectangle which I want give moving dash effect to it! If the Shape was Circle I could simply rotate the view with animation, since this is a Rectangle! I have no other idea to make that goal possible!
struct ContentView: View {
var body: some View {
Rectangle()
.strokeBorder(style: StrokeStyle(lineWidth: 5, dash: [10]))
.frame(width: 200, height: 200)
.foregroundColor(Color.blue)
}
}

This is sometimes called the “marching ants” effect.
Animate the dashPhase property of the StrokeStyle. You want the phase to go from 0 to the total length of the dash pattern. Since you only provided one value in the dash pattern, that value is used for the on-part and the off-part. So the total length of your pattern is 10 + 10 = 20.
struct ContentView: View {
#State var phase: CGFloat = 0
var body: some View {
Rectangle()
.strokeBorder(
style: StrokeStyle(
lineWidth: 5,
dash: [10],
dashPhase: phase))
.animation(
Animation.linear(duration: 1)
.repeatForever(autoreverses: false),
value: phase)
.frame(width: 200, height: 200)
.foregroundColor(Color.blue)
.onAppear {
phase = 20
}
}
}

Related

SwiftUI - Animate height of rectangle from 0 to height

I am attempting to animate the height of a RoundedRectangle() from 0 to its new height, I would like it to grow from its current position upwards when my device shakes. As it currently stands, I have a onShake function which I want to activate the growing of the rectangle.
struct View1: View {
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: 0, alignment: .center)
.foregroundColor(Color("RedColour"))
.onShake {
withAnimation {
self.frame(width: 100, height: 100)
}
}
}
}
}
As you can probably tell I am very new to SwiftUI and have very limited knowledge of how animations work. Feel free to grill me for it, but I would also love some help with attempting to grow this rectangle upwards when my device shakes.
Much love!
You could try this. But as the information you provided do not include how you want to use this, this might be incomplete.
struct View1: View {
#State private var height: CGFloat = 0 // add this to modify
// the height of the Rounded Rectangle
let targetHeight: CGFloat = 100 // targetHeight var to syncronise with the VStack
var body: some View {
ZStack {
Color.white
.edgesIgnoringSafeArea(.all)
Text("Hola")
.foregroundColor(.black)
.font(.headline)
.background{
VStack{
Spacer(minLength: 0)
RoundedRectangle(cornerRadius: 5)
.frame(width: 100, height: height)
.foregroundColor(.red)
.onShake {
withAnimation(.linear(duration: 5)) {
height = targetHeight
}
}
}.frame(height: targetHeight) // VStack with fixed height
//to animate from bottom up
}
}
}
}

Increase/Decrease the size of a view horizontally by dragging the edges of it

I've seen a few similar examples of this such as How to correctly do up an adjustable split view in SwiftUI? and How to resize UIView by dragging from its edges? but I can't find exactly what I'm looking for that correlates correctly across to SwiftUI
I have a view that I want the user to be able to adjust the width of via a 'grab bar' on the right of the view. When the user drags this bar left (decreases view width) and to the right (increases the view width). How can I go about doing this?
In the example, RedRectangle is my view that i'm trying to adjust which comprises of a Rectangle and the Resizer which is manipulated to adjust the size. What am I doing wrong here?
Additionally, there isn't a gradual animation/transition of the frame being increased/decreased and it just seems to jump. How can I achieve this?
Reproducible example linked here:
import SwiftUI
struct ContentView: View {
#State var resizedWidth: CGFloat?
var body: some View {
HStack(alignment: .center) {
Spacer()
RedRectangle(width: 175, resizedWidth: resizedWidth)
Resizer()
.gesture(
DragGesture()
.onChanged({ value in
resizedWidth = max(80, resizedWidth ?? 0 + value.translation.width)
})
)
Spacer()
}
}
}
struct RedRectangle: View {
let width: CGFloat
var resizedWidth: CGFloat?
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: resizedWidth != nil ? resizedWidth : width, height: 300)
.frame(minWidth: 80, maxWidth: 400)
}
}
struct Resizer: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 8, height: 75)
.cornerRadius(10)
}
}
import SwiftUI
struct ContentView: View {
let minWidth: CGFloat = 100
#State var width: CGFloat?
var body: some View {
HStack(alignment: .center) {
Spacer()
RedRectangle(width: width ?? minWidth)
Resizer()
.gesture(
DragGesture()
.onChanged { value in
width = max(minWidth, width! + value.translation.width)
}
)
Spacer()
}
.onAppear {
width = minWidth
}
}
}
struct RedRectangle: View {
let width: CGFloat
var body: some View {
Rectangle()
.fill(Color.red)
.frame(width: width, height: 100)
}
}
struct Resizer: View {
var body: some View {
Rectangle()
.fill(Color.blue)
.frame(width: 8, height: 75)
.cornerRadius(10)
}
}

SwiftUI view animates at unexpected path

I made the following PulsatingView:
struct PulsatingView: View {
#State private var opacity = 0.7
#State private var scale: CGFloat = 0.5
var body: some View {
VStack {
ZStack {
Circle()
.fill(Color.black.opacity(opacity))
.animation(.easeInOut(duration: 2).delay(1.5).repeatForever(autoreverses: false))
.frame(width: 7, height: 7)
.scaleEffect(scale)
.animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false))
Circle()
.fill(Color.black)
.frame(width: 7, height: 7)
}
}
.onAppear {
opacity = 0
scale = 5
}
}
}
It produces the following result: (click to see animation; it's a .gif)
However, when I add it to a view, inside a VStack for example, this happens: (click to see animation; it's a .gif)
I have no idea why this happens or how I can fix it. I've already tried adding modifiers such as .fixedSize(horizontal: true, vertical: true) or .frame(width: 50, height: 50) to it but that doesn't help.
Does anyone know what's going on?
Try using animation with joined value, like
Circle()
.fill(Color.black.opacity(opacity))
.animation(.easeInOut(duration: 2).delay(1.5).repeatForever(autoreverses: false), value: opacity) // << here !!
.frame(width: 7, height: 7)
.scaleEffect(scale)
.animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false), value: scale) // << here !!
Important: if it is started in NavigationView then animatable state should be activated postponed. The reason and investigation details was provided in https://stackoverflow.com/a/66643096/12299030.
This issue is not about your code it is because NavigationView to fix it we should use DispatchQueue.main.async I wanted refactor your code as well, hope help you, also you can avoid hard coded value as much as possible, for example you can apply the frame in outside of view:
struct ContentView: View {
var body: some View {
CircleView(lineWidth: 0.5, color: .red, scale: 5.0)
.frame(width: 7, height: 7)
}
}
struct CircleView: View {
let lineWidth: CGFloat
let color: Color
let scale: CGFloat
#State private var startAnimation: Bool = Bool()
var body: some View {
Circle()
.strokeBorder(style: .init(lineWidth: startAnimation ? .zero : lineWidth)).opacity(startAnimation ? .zero : 1.0)
.scaleEffect(startAnimation ? scale : 1.0)
.overlay( Circle() )
.foregroundColor(color)
.onAppear() { DispatchQueue.main.async { startAnimation.toggle() } }
.animation(.easeIn(duration: 2.5).delay(1).repeatForever(autoreverses: false), value: startAnimation)
}
}
I finally managed to find a solution to this on my own project - I found the only solution to get the animated view working within a NavigationView was to move the animation inside the onAppear call, like this:
Circle()
.fill(Color.black.opacity(opacity))
.onAppear {
withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: false)) {
opacity = 0
}
}
Hope this can help someone else if they get stuck on the same issue I ended up having!

How to have text fit within boundaries of circular shape/image SwiftUI

I can't seem to grasp how to make sure that my text overlays and fits within the circular image's boundaries. This is what I have so far.
Circle()
.frame(width: 100, height: 100)
.overlay(
Text("Reaaaallllllyyyyyy Looooooongggggg")
.foregroundColor(Color.red)
.frame(minWidth: 0, maxWidth: .infinity)
)
Is there a way in SwiftUI to get this to work? How would I go about achieving this?
struct ClippedCircleView: View {
#State var text: String = "Reaaaallllllyyyyyy Looooooongggggg"
// Make this any number you are comfortable with
#State var letterLimit: Int = 12
var body: some View {
Circle()
.frame(width: 100, height: 100)
.overlay(
Text(text)
//Limit to 1 line until your letterLimit
.lineLimit(text.count <= letterLimit ? 1 : nil)
//Clips it to shape
.clipShape(ContainerRelativeShape()).padding()
.foregroundColor(Color.red)
//This makes super tiny letters so adjust to your use case
.minimumScaleFactor(0.1)
)
}
}

How do I make all views the same height in a SwiftUI View with an HStack?

I want a simple graph with a colored rectangle of variable height for each data point.
The white space below the colored rectangle should expand so that the bottom numbers line up, the way the top row of numbers does.
This is my view:
So I would like an idiomatic solution to getting the bottom row of numbers to line up with the 59. Any advice that points me in the right direction is welcome. Thanks.
Here's what I have so far:
struct DemoView: View {
var dataSource = [1, 0, 34, 12, 59, 44]
/// Provide a Dynamic Height Based on the Tallest View in the Row
#State private var height: CGFloat = .zero // < calculable height
/// The main view is a row of variable height views
var body: some View {
HStack(alignment: .top) {
Spacer()
/// i want these to all be the same height
ForEach(0 ..< 6) { index in
VStack {
Text("\(index)")
Rectangle()
.fill(Color.orange)
.frame(width: 20, height: CGFloat(self.dataSource[index]))
Text("\(dataSource[index])")
}
}
Spacer()
}
.alignmentGuide(.top, computeValue: { d in
DispatchQueue.main.async {
self.height = max(d.height, self.height)
}
return d[.top]
})
}
}
struct Demo_Preview: PreviewProvider {
static var previews: some View {
DemoView()
}
}
Edit to show the final results:
I made the changes Asperi suggested, changed the .top alignments to .bottom and got a very nice simple chart:
Here is possible (seems simplest) approach. Tested with Xcode 12 / iOS 14
struct DemoView: View {
var dataSource = [1, 0, 34, 12, 59, 44]
var body: some View {
HStack(alignment: .top) {
Spacer()
/// i want these to all be the same height
ForEach(0 ..< 6) { index in
VStack {
Text("\(index)")
Color.clear
.frame(width: 20, height: CGFloat(dataSource.max() ?? 0))
.overlay(
Color.orange
.frame(height: CGFloat(self.dataSource[index]))
, alignment: .top)
Text("\(dataSource[index])")
}
}
Spacer()
}
}
}