[英]How to implement the same iOS 16 lock screen circular widget myself?
I'm trying to implement lock screen widget myself我正在尝试自己实现锁屏小部件
Widget I currently implement我目前实现的小部件
I want to implement this ios16 lock screen widget我想实现这个ios16锁屏小部件
I've made almost everything, but I haven't been able to implement the small circle's transparent border.我几乎什么都做了,就是没能实现小圆圈的透明边框。 I couldn't find a way to make even the background of the ring behind it transparent.
我找不到使它后面的环的背景变得透明的方法。
My code我的代码
struct RingTipShape: Shape { // small circle
var currentPercentage: Double
var thickness: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
let angle = CGFloat((240 * currentPercentage) * .pi / 180)
let controlRadius: CGFloat = rect.width / 2 - thickness / 2
let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
let x = center.x + controlRadius * cos(angle)
let y = center.y + controlRadius * sin(angle)
let pointCenter = CGPoint(x: x, y: y)
path.addEllipse(in:
CGRect(
x: pointCenter.x - thickness / 2,
y: pointCenter.y - thickness / 2,
width: thickness,
height: thickness
)
)
return path
}
var animatableData: Double {
get { return currentPercentage }
set { currentPercentage = newValue }
}
}
struct RingShape: Shape {
var currentPercentage: Double
var thickness: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.width / 2, y: rect.height / 2), radius: rect.width / 2 - (thickness / 2), startAngle: Angle(degrees: 0), endAngle: Angle(degrees: currentPercentage * 240), clockwise: false)
return path.strokedPath(.init(lineWidth: thickness, lineCap: .round, lineJoin: .round))
}
var animatableData: Double {
get { return currentPercentage}
set { currentPercentage = newValue}
}
}
struct CircularWidgetView: View { // My customizing widget view
@State var percentage: Double = 1.0
var body: some View {
GeometryReader { geo in
ZStack {
RingBackgroundShape(thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white.opacity(0.21))
RingShape(currentPercentage: 0.5, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white.opacity(0.385))
RingTipShape(currentPercentage: 0.5, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.frame(width: geo.size.width, height: geo.size.height)
.foregroundColor(.white)
/*
I want to make RingTipShape completely
transparent. Ignoring even the RingShape behind it
*/
VStack(spacing: 4) {
Image(systemName: "scooter")
.resizable()
.frame(width: 24, height: 24)
Text("hello")
.font(.system(size: 10, weight: .semibold))
.lineLimit(1)
.minimumScaleFactor(0.1)
}
}
}
}
}
How can I make a transparent border that also ignores the background of the view behind it?如何制作透明边框,同时忽略其背后视图的背景?
This is a great exercise.这是一个很好的练习。 The missing piece is a mask.
缺少的部分是面具。
Note: Despite the fact that there are numerous ways to improve the existing code, I will try to stick to the original solution since the point is to gain experience through practice (based on the comments).注意:尽管有很多方法可以改进现有代码,但我会尽量坚持原来的解决方案,因为重点是通过实践获得经验(基于评论)。 However I will share some tips at the end.
不过,我会在最后分享一些技巧。
So we can think of it in two steps:所以我们可以分两步来思考:
RingTipShape
at the same (centered) position as our existing but a bit larger.RingTipShape
与我们现有的相同(居中)position 但更大一点。 The first point is an easy one, we just need to define the outer thickness in order to place the ellipse on top of the track at the correct location:第一点很简单,我们只需要定义外部厚度,以便将椭圆放置在轨道顶部的正确位置:
struct RingTipShape: Shape { // small circle
//...
let outerThickness: CGFloat
//...
let controlRadius: CGFloat = rect.width / 2 - outerThickness / 2
//...
}
then our existing code changes to:然后我们现有的代码更改为:
RingTipShape(currentPercentage: percentage, thickness: 5.5, outerThickness: 5.5)
now for the second part we need something to create a larger circle, which is easy:现在对于第二部分,我们需要一些东西来创建一个更大的圆,这很容易:
RingTipShape(currentPercentage: percentage, thickness: 10.0, outerThickness: 5.5)
ok so now for the final part, we are going to use this (larger) shape to create a kind of inverted mask:好的,现在是最后一部分,我们将使用这个(更大的)形状来创建一种倒置的蒙版:
private var thumbMask: some View {
ZStack {
Color.white // This part will be transparent
RingTipShape(currentPercentage: percentage, thickness: 10.0, outerThickness: 5.5)
.fill(Color.black) // This will be masked out
.rotationEffect(Angle(degrees: 150))
}
.compositingGroup() // Rasterize the shape
.luminanceToAlpha() // Map luminance to alpha values
}
and we apply the mask like this:我们像这样应用面具:
RingShape(currentPercentage: percentage, thickness: 5.5)
.rotationEffect(Angle(degrees: 150))
.foregroundColor(.white.opacity(0.385))
.mask(thumbMask)
which results to this:结果是:
Some observations/tips:一些观察/提示:
GeometryReader
(and all the frame modifiers) in your CircularWidgetView
, the ZStack
will offer all available space to views.CircularWidgetView
中的GeometryReader
(和所有框架修改器), ZStack
将为视图提供所有可用空间。.aspectRatio(contentMode: .fit)
to your image in order to avoid stretching..aspectRatio(contentMode: .fit)
添加到图像中以避免拉伸。 For example:例如:
struct MyGauge: View {
let value: Double = 0.5
let range = 0.1...0.9
var body: some View {
ZStack {
// Backing track
track().opacity(0.2)
// Value track
track(showsProgress: true)
}
}
private var mappedValue: Double {
(range.upperBound + range.lowerBound) * value
}
private func track(showsProgress: Bool = false) -> some View {
Circle()
.trim(from: range.lowerBound, to: showsProgress ? mappedValue : range.upperBound)
.stroke(.white, style: .init(lineWidth: 5.5, lineCap: .round))
.rotationEffect(.radians(Double.pi / 2))
}
}
would result to:会导致:
which simplifies things a bit by utilizing the trim
modifier.这通过使用
trim
修饰符简化了一些事情。
I hope that this makes sense.我希望这是有道理的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.