I edited my question. I shared full code.
I want to bend the end points of the semicircle as in the 1st image. How can I do that ? I tried this code but got an unrelated error.
How can I soften the corners of a half circle?
I created a customShape but I couldn't get the look I wanted.
.clipShape(
circle()
.trim(from: 0.5, to: 1)
.stroke(Color.red, style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
Error:
ChartData Container:
class ChartDataContainer : ObservableObject {
#Published var chartData =
[ChartData(color: Color(#colorLiteral(red: 1, green: 0.4932718873, blue: 0.4739984274, alpha: 1)), percent: 8, value: 0),
ChartData(color: Color(#colorLiteral(red: 1, green: 0.8323456645, blue: 0.4732058644, alpha: 1)), percent: 15, value: 0),
ChartData(color: Color(#colorLiteral(red: 0.4508578777, green: 0.9882974029, blue: 0.8376303315, alpha: 1)), percent: 32, value: 0),
ChartData(color: Color(#colorLiteral(red: 0.476841867, green: 0.5048075914, blue: 1, alpha: 1)), percent: 45, value: 0)]
func calc(){
var value : CGFloat = 0
for i in 0..<chartData.count {
value += chartData[i].percent
chartData[i].value = value
}
}
}
Custom Shape:
struct CustomShape: View {
var body: some View {
HalfCircle()
.stroke(.red, style: StrokeStyle(lineWidth: 30, lineCap: .round, lineJoin: .round))
.frame(width: 300, height: 300)
}
}
struct CustomShape_Previews: PreviewProvider {
static var previews: some View {
CustomShape()
}
}
struct HalfCircle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.height/2, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180), clockwise: true)
return path
}
}
Code:
struct HalfDonutChart : View {
#ObservedObject var charDataObj = ChartDataContainer()
#State var indexOfTappedSlice = -1
var body: some View {
ZStack {
ZStack {
ForEach(0..<charDataObj.chartData.count) { index in
Circle()
.trim(from: 0.5, to: 1)
.trim(from: index == 0 ? 0.0 : charDataObj.chartData[index-1].value/100, to: charDataObj.chartData[index].value/100)
.stroke(charDataObj.chartData[index].color,lineWidth: 30)
.onTapGesture {
indexOfTappedSlice = indexOfTappedSlice == index ? -1 : index
}
.scaleEffect(index == indexOfTappedSlice ? 1.1 : 1.0)
.animation(.spring())
}
if indexOfTappedSlice != -1 {
Text(String(format: "%.2f", Double(charDataObj.chartData[indexOfTappedSlice].percent))+"%")
.font(.title)
}
}
.mask(
CustomShape()
)
.frame(width: 200, height: 250)
.padding()
.onAppear() {
self.charDataObj.calc()
}
}
}
}
Related
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.
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()
}
}
}
i have this card thing in the picture to build, here're the code:
struct ArticleCard: View {
var article: Article
var body: some View {
ZStack {
Color.white
.border(Color(red: 0, green: 0, blue: 0, opacity: 0.2))
.shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.2), radius: 0.0, x: 5, y: 5)
VStack{
Text(article.title)
.padding(.top, 5)
.padding(.leading)
.padding(.trailing)
.font(.title2)
Image(article.coverUrl).resizable()
.frame(height: 200)
.padding(.leading)
.padding(.trailing)
Text(article.title)
.padding(.leading)
.padding(.trailing)
.padding(.bottom)
}
}
}
}
in the ZStack i put a Color.white there as a background of the card, and give this color view a shadow, but the color seems to be transparent, therefor i got unwanted lines on the top and the left inside the borders, how do i get rid of them?
You are using border and shadow in wrong place, try this:
PS: there is no reason of using Color(red: 0, green: 0, blue: 0, opacity: 0.2) you can use this: Color.black.opacity(0.2)
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color.white
VStack {
Text("article.title")
.padding()
Image(systemName: "star")
.resizable()
.scaledToFit()
Text("article.title")
.padding()
}
}
.frame(width: 300, height: 300, alignment: .center)
.border(Color(red: 0, green: 0, blue: 0, opacity: 0.2))
.compositingGroup()
.shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.2), radius: 0.0, x: 5, y: 5)
}
}
I refactored your code for better and less code as possible you can use Image instead of Text in your App.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("your custom Text")
.padding()
Text("🥩")
.font(Font.system(size: 150))
Text("your custom Text")
.padding()
}
.frame(width: 300, height: 300, alignment: .center)
.background(Color.white)
.border(Color.black.opacity(0.2))
.compositingGroup()
.shadow(color: Color.black.opacity(0.2), radius: 0.0, x: 5, y: 5)
}
}
I am trying to create a shaded cross-hatched. But so far I can do it by adding a Image.
How can I create a custom view in which lines will be drawn and not filled with a Image?
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Image("lineFilledBG").resizable().clipShape(Circle())
Circle().stroke()
Circle().foregroundColor(.yellow).opacity(0.3)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is how it looks now. Want lines to draw on top of another view or shape, without adding opacity and image pattern filling.
Thank to Cenk Bilgen for stripe pattern. Tweaked a bit so that you can rotate the hatch for any shape.
import SwiftUI
import CoreImage.CIFilterBuiltins
extension CGImage {
static func generateStripePattern(
colors: (UIColor, UIColor) = (.clear, .black),
width: CGFloat = 6,
ratio: CGFloat = 1) -> CGImage? {
let context = CIContext()
let stripes = CIFilter.stripesGenerator()
stripes.color0 = CIColor(color: colors.0)
stripes.color1 = CIColor(color: colors.1)
stripes.width = Float(width)
stripes.center = CGPoint(x: 1-width*ratio, y: 0)
let size = CGSize(width: width, height: 1)
guard
let stripesImage = stripes.outputImage,
let image = context.createCGImage(stripesImage, from: CGRect(origin: .zero, size: size))
else { return nil }
return image
}
}
extension Shape {
func stripes(angle: Double = 45) -> AnyView {
guard
let stripePattern = CGImage.generateStripePattern()
else { return AnyView(self)}
return AnyView(Rectangle().fill(ImagePaint(
image: Image(decorative: stripePattern, scale: 1.0)))
.scaleEffect(2)
.rotationEffect(.degrees(angle))
.clipShape(self))
}
}
And usage
struct ContentView: View {
var body: some View {
VStack {
Rectangle()
.stripes(angle: 30)
Circle().stripes()
Capsule().stripes(angle: 90)
}
}
}
Output image link
I'm not sure this is what you want, but CoreImage can generate a striped pattern.
import CoreImage.CIFilterBuiltins
import SwiftUI
extension CGImage {
// width is for total, ratio is second stripe relative to full width
static func stripes(colors: (UIColor, UIColor), width: CGFloat, ratio: CGFloat) -> CGImage {
let filter = CIFilter.stripesGenerator()
filter.color0 = CIColor(color: colors.0)
filter.color1 = CIColor(color: colors.1)
filter.width = Float(width-width*ratio)
filter.center = CGPoint(x: width, y: 0)
let size = CGSize(width: width+width*ratio, height: 1)
let bounds = CGRect(origin: .zero, size: size)
// keep a reference to a CIContext if calling this often
return CIContext().createCGImage(filter.outputImage!.clamped(to: bounds), from: bounds)!
}
}
then use ImagePaint
Circle().fill(
ImagePaint(image: Image(decorative: CGImage.stripes(colors: (.blue, .yellow), width: 80, ratio: 0.25), scale: 1))
)
.rotationEffect(.degrees(-30))
Or CILineScreen filter may be more what you want.
I`m not sure what do you want but another possible solution is using different backgrounds in a ZStack linear gradient and shadows to make convex or concave effect., for this it helps that your background with a little color, not white white. some samples:
The code:
struct ContentView: View {
var body: some View {
ZStack {
Color.yellow.opacity(0.1)
VStack(spacing: 50) {
myCircle()
myCircle1()
}
}
}
}
struct myCircle: View {
var body: some View {
Circle().foregroundColor(Color.clear)
.frame(width: 100, height: 100)
.background(
ZStack {
LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1) ), Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1))]), startPoint: .topLeading, endPoint: .bottomTrailing)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)), radius: 3, x: -5, y: -5)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: Color(#colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)), radius: 3, x: 3, y: 3)
}
)
.clipShape(Circle())
.shadow(color: Color( #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) ), radius: 20, x: -20, y: -20)
.shadow(color: Color( #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1) ), radius: 20, x: 20, y: 20)
}
}
struct myCircle1: View {
var body: some View {
Circle().foregroundColor(Color.clear)
.frame(width: 100, height: 100)
.background(
ZStack {
LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) ), Color(#colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1))]), startPoint: .topLeading, endPoint: .bottomTrailing)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)), radius: 3, x: -5, y: -5)
Circle()
.stroke(Color.clear, lineWidth: 10)
.shadow(color: Color(#colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)), radius: 3, x: 3, y: 3)
}
)
.clipShape(Circle())
.shadow(color: Color( #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) ), radius: 20, x: -20, y: -20)
.shadow(color: Color( #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1) ), radius: 20, x: 20, y: 20)
}
}
Thank you to Cenk Bilgen and flyer2001, I edited the code and come up with this code so it can be used with a view.
import SwiftUI
import CoreImage.CIFilterBuiltins
extension CGImage {
static func generateStripePattern(
colors: (UIColor, UIColor) = (.clear, .black),
width: CGFloat = 6,
ratio: CGFloat = 1) -> CGImage? {
let context = CIContext()
let stripes = CIFilter.stripesGenerator()
stripes.color0 = CIColor(color: colors.0)
stripes.color1 = CIColor(color: colors.1)
stripes.width = Float(width)
stripes.center = CGPoint(x: 1 - (width * ratio), y: 0)
let size = CGSize(width: width, height: 1)
guard
let stripesImage = stripes.outputImage,
let image = context.createCGImage(stripesImage, from: CGRect(origin: .zero, size: size))
else { return nil }
return image
}
}
struct ViewStripes : ViewModifier {
var stripeColor : Color
var width : CGFloat
var ratio : CGFloat
var angle : Double
var frameW : CGFloat
var frameH : CGFloat
func body(content: Content) -> some View {
if let stripePattern = CGImage.generateStripePattern(colors: (.clear,UIColor(stripeColor)), width: width, ratio: ratio) {
content
.overlay(Rectangle().fill(ImagePaint(image: Image(decorative: stripePattern, scale: 1.0)))
.frame(width: hypotenuse((frameW / 2), frameH / 2) * 2, height: hypotenuse(frameW / 2, frameH / 2) * 2)
//.scaleEffect(1)
.rotationEffect(.degrees(angle))
.mask(content))
}
}
}
extension View {
func drawStripes(stripeColor: Color, width: CGFloat, ratio: CGFloat, angle : Double, frameW : CGFloat, frameH : CGFloat) -> some View {
modifier(ViewStripes(stripeColor: stripeColor, width: width, ratio: ratio, angle: angle, frameW: frameW, frameH: frameH))
}
}
func hypotenuse(_ a: Double, _ b: Double) -> Double {
return (a * a + b * b).squareRoot()
}
And for usage:
Rectangle()
.foregroundColor(Color.Red)
.frame(width:20, height: 20)
//This is the code itself
.drawStripes(stripeColor: Color.white, width: 3, ratio: 0.9, angle: 45, frameW: 20, frameH: 20)
Hope it may be helpful for someone.
Based on the value of a #State var, how can I animate any other parameters but one? For instance, I want to animate .offset but not the .foregroundColor. It may sound like an easy task to do, but it's not so easy when using the same "base view" to create some sort of cell views and combining different modifiers and transition.
For the following animation, I'm trying to "avoid" that change-color animation while allowing the offset to happen - I need the change of color to happen instantaneously, no animation.
For this animation, I'm also trying to "avoid" that change-color animation. The challenge here is that the base view also has a transition.
The "base view" name is LogoView. Here's my code:
struct Test_ForegroundColorAnimation: View {
var array: [Color] = [.yellow, .green, .blue]
#State var activeIndex1: Int = -1
#State var activeIndex2: Int = -1
#State var show: Bool = true
private func animationFor(index: Int) -> Animation {
Animation
.easeInOut(duration: 0.85)
.delay(0.15 * Double(array.count - index))
}
func zindexfor(index: Int) -> Double {
Double(index == activeIndex1 ? 100 : index)
}
func opacityfor(index: Int) -> Double {
self.activeIndex1 >= 0 ? (index == self.activeIndex1 ? 1 : 0) : 1
}
var body: some View {
VStack(alignment: .leading, spacing: 25) {
Spacer()
VStack {
Text("Offset animation").font(.title)
Text("Click any item").font(.subheadline)
}
ZStack {
ForEach(array.indices, id: \.self) { index in
LogoView(isSelected: index == self.activeIndex1, color: self.array[index])
.offset(x: self.activeIndex1 == -1 ? CGFloat(90 * index) : 0)
.zIndex(self.zindexfor(index: index))
.opacity(self.opacityfor(index: index))
.animation(self.animationFor(index: index))
.onTapGesture {
self.activeIndex1 = index == self.activeIndex1 ? -1 : index
}
}
}
.padding(.bottom, 70)
VStack {
Text("Transition").font(.title)
Text("Click any item").font(.subheadline)
}
Button(action: {
self.show.toggle()
}) {
Color.purple
.frame(width: 100, height: 70)
.overlay(Text("Activate transition!").foregroundColor(.white))
}
HStack(spacing: 5) {
ForEach(array.indices, id: \.self) { index in
HStack {
if self.show {
LogoView(isSelected: index == self.activeIndex2, color: self.array[index])
.transition(AnyTransition.move(edge: .bottom))
.onTapGesture {
self.activeIndex2 = index == self.activeIndex2 ? -1 : index
}
}
}
.frame(width: 90, height: 90)
.animation(self.animationFor(index: index))
}
}
}
}
}
struct Test_ForegroundColorAnimation_Previews: PreviewProvider {
static var previews: some View {
Test_ForegroundColorAnimation()
}
}
struct LogoView: View {
var isSelected: Bool = false
var color: Color = Color(#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1))
var shadow: Color = Color(#colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1))
var body: some View {
Image(systemName: "heart.fill")
.resizable()
.frame(width: 28, height: 28)
.foregroundColor(isSelected ? Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)) : color)
.padding()
.overlay(
Circle()
.stroke(lineWidth: 3)
.foregroundColor(color)
)
.background(
Circle()
.foregroundColor(isSelected ? color : Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)))
.shadow(color: isSelected ? color.opacity(0.7) : Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)).opacity(0.3), radius: 5, x: 0, y: 10)
)
.frame(width: 90, height: 90)
}
}
If I correctly understood what you need, here is a fix
struct LogoView: View {
var isSelected: Bool = false
var color: Color = Color(#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1))
var shadow: Color = Color(#colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1))
var body: some View {
Image(systemName: "heart.fill")
.resizable()
.frame(width: 28, height: 28)
.foregroundColor(isSelected ? Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)) : color)
.padding()
.overlay(
Circle()
.stroke(lineWidth: 3)
.foregroundColor(color)
)
.background(
Circle()
.foregroundColor(isSelected ? color : Color(#colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)))
.animation(nil) // << fix !!
.shadow(color: isSelected ? color.opacity(0.7) : Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)).opacity(0.3), radius: 5, x: 0, y: 10)
)
.frame(width: 90, height: 90)
}
}