xcode 自定义形状上的垂直居中文本- SwiftUI

pes8fvy9  于 2023-05-19  发布在  Swift
关注(0)|答案(2)|浏览(208)

我创建了一个自定义形状的形式环段。我最终想在我的应用程序中使用这个形状作为按钮,因此我需要能够将文本定位在它的正中间。下面的图片显示了环段(蓝色)和文本(红色),我希望在蓝色段内垂直居中。

下面的代码创建自定义蓝色形状段:

struct CurvedButtonData {
    enum ButtonLocation {
        case top
        case right
        case bottom
        case left
    }

    /// The  button's width as an angle in degrees.
    let width: Double = 90.0

    /// The button's start location, as an angle in degrees.
    fileprivate(set) var start = 0.0
    /// The button's end location, as an angle in degrees.
    fileprivate(set) var end = 0.0

    init(position: ButtonLocation) {
        switch position {
        case .top:
            start = -135
        case .right:
            start = -45
        case .bottom:
            start = 45
        case .left:
            start = 135
        }

        end = start + width
    }
}

struct CurvedButton: Shape, InsettableShape {
    let data: CurvedButtonData
    var insetAmount = 0.0

    func path(in rect: CGRect) -> Path {
        let points = CurvedButtonGeometry(curvedButtonData: data, rect: rect)

        var path = Path()

        path.addArc(center: points.center, radius: points.innerRadius, startAngle: .degrees(data.start), endAngle: .degrees(data.end), clockwise: false)

        path.addArc(center: points.center, radius: points.outerRadius - insetAmount, startAngle: .degrees(data.end), endAngle: .degrees(data.start), clockwise: true)

        path.closeSubpath()

        return path
    }

    func inset(by amount: CGFloat) -> CurvedButton {
        var button = self
        button.insetAmount += amount
        return button
    }
}

struct CurvedButtonGeometry {
    let data: CurvedButtonData
    let center: CGPoint
    let innerRadius: CGFloat
    let outerRadius: CGFloat

    init(curvedButtonData: CurvedButtonData, rect: CGRect) {
        center = CGPoint(x: rect.midX, y: rect.midY)
        let radius = min(rect.width, rect.height) / 4
        innerRadius = radius
        outerRadius = radius * 2
        data = curvedButtonData
    }

    /// Returns the view location of the point in the wedge at unit-
    /// space location `unitPoint`, where the X axis of `p` moves around the
    /// wedge arc and the Y axis moves out from the inner to outer
    /// radius.
    subscript(unitPoint: UnitPoint) -> CGPoint {
        let radius = lerp(innerRadius, outerRadius, by: unitPoint.y)
        let angle = lerp(data.start, data.end, by: Double(unitPoint.x))

        return CGPoint(x: center.x + CGFloat(cos(angle)) * radius,
                       y: center.y + CGFloat(sin(angle)) * radius)
    }

    /// Linearly interpolate from `from` to `to` by the fraction `amount`.
    private func lerp<T: BinaryFloatingPoint>(_ fromValue: T, _ toValue: T, by amount: T) -> T {
        return fromValue + (toValue - fromValue) * amount
    }
}

这是我可怜的尝试,用我的自定义形状创建一个按钮,并将“Click me”文本置于蓝色区域的中心:

import SwiftUI

struct CircularCVBControl: View {
    var body: some View {
        Button {

        } label: {
            ZStack(alignment: .top) {
                CurvedButton(data: .init(position: .top))
                    .fill(.blue)
                    .overlay(alignment: .top) {
                        Text("Click me")
                            .foregroundColor(.red)
                    }
            }
        }
        .buttonStyle(.plain)
    }
}

struct CircularCVBControl_Previews: PreviewProvider {
    static var previews: some View {
        CircularCVBControl()
    }
}

我现在怎么能整齐(和 * 没有使用一个hacky填充 * 的文本)中心的文本垂直在我的自定义形状?我正在寻找一些自定义对齐指南/逻辑排序的工作。此外,形状也可以在其他方向上旋转,文本也应该随之旋转。

erhoui1w

erhoui1w1#

问题是你的曲线没有在帧的中间渲染。为了解决这个问题,我认为你需要抵消你的中心。就像这样:

center = CGPoint(x: rect.midX, y: rect.midY + innerRadius + (outerRadius - innerRadius)/2)

这是一个仅适用于“.top”位置的解决方案。其他的职位就交给你了;- )
下面是完整的代码:

import SwiftUI

struct CurvedButtonData {
    enum ButtonLocation {
        case top
        case right
        case bottom
        case left
    }

    /// The  button's width as an angle in degrees.
    let width: Double = 90.0

    /// The button's start location, as an angle in degrees.
    fileprivate(set) var start = 0.0
    /// The button's end location, as an angle in degrees.
    fileprivate(set) var end = 0.0

    init(position: ButtonLocation) {
        switch position {
        case .top:
            start = -135
        case .right:
            start = -45
        case .bottom:
            start = 45
        case .left:
            start = 135
        }

        end = start + width
    }
}

struct CurvedButton: Shape, InsettableShape {
    let data: CurvedButtonData
    var insetAmount = 0.0

    func path(in rect: CGRect) -> Path {
        let points = CurvedButtonGeometry(curvedButtonData: data, rect: rect)

        var path = Path()

        path.addArc(center: points.center, radius: points.innerRadius, startAngle: .degrees(data.start), endAngle: .degrees(data.end), clockwise: false)

        path.addArc(center: points.center, radius: points.outerRadius - insetAmount, startAngle: .degrees(data.end), endAngle: .degrees(data.start), clockwise: true)

        path.closeSubpath()

        return path
    }

    func inset(by amount: CGFloat) -> CurvedButton {
        var button = self
        button.insetAmount += amount
        return button
    }
}

struct CurvedButtonGeometry {
    let data: CurvedButtonData
    let center: CGPoint
    let innerRadius: CGFloat
    let outerRadius: CGFloat

    init(curvedButtonData: CurvedButtonData, rect: CGRect) {
        let radius = min(rect.width, rect.height) / 4
        innerRadius = radius
        outerRadius = radius * 2
        center = CGPoint(x: rect.midX, y: rect.midY + innerRadius + (outerRadius - innerRadius)/2)
        data = curvedButtonData
    }

    /// Returns the view location of the point in the wedge at unit-
    /// space location `unitPoint`, where the X axis of `p` moves around the
    /// wedge arc and the Y axis moves out from the inner to outer
    /// radius.
    subscript(unitPoint: UnitPoint) -> CGPoint {
        let radius = lerp(innerRadius, outerRadius, by: unitPoint.y)
        let angle = lerp(data.start, data.end, by: Double(unitPoint.x))

        return CGPoint(x: center.x + CGFloat(cos(angle)) * radius,
                       y: center.y + CGFloat(sin(angle)) * radius)
    }

    /// Linearly interpolate from `from` to `to` by the fraction `amount`.
    private func lerp<T: BinaryFloatingPoint>(_ fromValue: T, _ toValue: T, by amount: T) -> T {
        return fromValue + (toValue - fromValue) * amount
    }
}


struct CircularCVBControl: View {
    var body: some View {
        Button {

        } label: {
            ZStack() {
                CurvedButton(data: .init(position: .top))
                    .fill(.blue)
                    Text("Click me")
                        .foregroundColor(.red)
            }
        }
        .buttonStyle(.plain)
    }
}

struct CircularCVBControl_Previews: PreviewProvider {
    static var previews: some View {
        CircularCVBControl()
    }
}
2hh7jdfx

2hh7jdfx2#

我看到你想把多个Curvedbuttons组合在一起作为一个“圆”。在这种情况下,您确实需要偏移文本而不是形状。你可以使用GeometryReader。我已经为.top位置实现了它,如下所示。您可以将其他位置添加到getOffset()方法中。我对这段代码并不完全满意,因为有些坐标不是在一个以上的地方计算的。你可以/应该清理这个代码很多,但我想展示的核心原则,而不改变一切。希望这能帮上忙。

import SwiftUI

struct CurvedButtonData {
    enum ButtonLocation {
        case top
        case right
        case bottom
        case left
    }

    /// The  button's width as an angle in degrees.
    let width: Double = 90.0

    /// The button's start location, as an angle in degrees.
    fileprivate(set) var start = 0.0
    /// The button's end location, as an angle in degrees.
    fileprivate(set) var end = 0.0

    init(position: ButtonLocation) {
        switch position {
        case .top:
            start = -135
        case .right:
            start = -45
        case .bottom:
            start = 45
        case .left:
            start = 135
        }

        end = start + width
    }
}

struct CurvedButton: Shape, InsettableShape {
    let data: CurvedButtonData
    var insetAmount = 0.0

    func path(in rect: CGRect) -> Path {
        let points = CurvedButtonGeometry(curvedButtonData: data, rect: rect)

        var path = Path()

        path.addArc(center: points.center, radius: points.innerRadius, startAngle: .degrees(data.start), endAngle: .degrees(data.end), clockwise: false)

        path.addArc(center: points.center, radius: points.outerRadius - insetAmount, startAngle: .degrees(data.end), endAngle: .degrees(data.start), clockwise: true)

        path.closeSubpath()

        return path
    }

    func inset(by amount: CGFloat) -> CurvedButton {
        var button = self
        button.insetAmount += amount
        return button
    }
}

struct CurvedButtonGeometry {
    let data: CurvedButtonData
    let center: CGPoint
    let innerRadius: CGFloat
    let outerRadius: CGFloat

    init(curvedButtonData: CurvedButtonData, rect: CGRect) {
        let radius = min(rect.width, rect.height) / 4
        innerRadius = radius
        outerRadius = radius * 2
        center = CGPoint(x: rect.midX, y: rect.midY)
        data = curvedButtonData
    }

    /// Returns the view location of the point in the wedge at unit-
    /// space location `unitPoint`, where the X axis of `p` moves around the
    /// wedge arc and the Y axis moves out from the inner to outer
    /// radius.
    subscript(unitPoint: UnitPoint) -> CGPoint {
        let radius = lerp(innerRadius, outerRadius, by: unitPoint.y)
        let angle = lerp(data.start, data.end, by: Double(unitPoint.x))

        return CGPoint(x: center.x + CGFloat(cos(angle)) * radius,
                       y: center.y + CGFloat(sin(angle)) * radius)
    }

    /// Linearly interpolate from `from` to `to` by the fraction `amount`.
    private func lerp<T: BinaryFloatingPoint>(_ fromValue: T, _ toValue: T, by amount: T) -> T {
        return fromValue + (toValue - fromValue) * amount
    }
}


struct CircularCVBControl: View {
    var body: some View {
        Button {

        } label: {
            GeometryReader { geo in
                CurvedButton(data: .init(position: .top))
                    .foregroundColor(.blue)
                    .overlay(
                        Text("Click me")
                            .foregroundColor(.red)
                            .offset(y: getOffset(geo))
                    )
            }
        }
        .buttonStyle(.plain)
    }
    
    func getOffset(_ geo : GeometryProxy) -> CGFloat {
        let radius = min(geo.size.width, geo.size.height) / 4
        let innerRadius = radius
        let outerRadius = radius * 2
        let offset = -1 * innerRadius - (outerRadius - innerRadius)/2
        return offset
    }
}

struct CircularCVBControl_Previews: PreviewProvider {
    static var previews: some View {
        CircularCVBControl()
    }
}

相关问题