web-dev-qa-db-fra.com

SwiftUI: Comment dessiner une forme remplie et caressée?

Dans UIKit, dessiner un chemin/une forme caressé et rempli est assez facile.

Par exemple, le code ci-dessous dessine un cercle rouge qui est caressé en bleu.

override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }

    let center = CGPoint(x: rect.midX, y: rect.midY)

    ctx.setFillColor(UIColor.red.cgColor)
    ctx.setStrokeColor(UIColor.blue.cgColor)

    let arc = UIBezierPath(arcCenter: center, radius: rect.width/2, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)

    arc.stroke()
    arc.fill()
}

Comment faire cela avec SwiftUI?

L'interface utilisateur rapide semble prendre en charge:

Circle().stroke(Color.blue)
// and/or
Circle().fill(Color.red)

mais non

Circle().fill(Color.red).stroke(Color.blue) // Value of type 'ShapeView<StrokedShape<Circle>, Color>' has no member 'fill'
// or 
Circle().stroke(Color.blue).fill(Color.red) // Value of type 'ShapeView<Circle, Color>' has no member 'stroke'

Suis-je censé simplement ZStack deux cercles? Cela semble un peu idiot.

16
orj

Vous pouvez dessiner un cercle avec trait au-dessus d'un cercle rempli

struct ContentView: View {
    var body: some View {
        Circle()
        .overlay(
            Circle()
           .stroke(Color.green,lineWidth: 5)
          ).foregroundColor(Color.red)
   }
}

enter image description here

15
Imran

Il semble que ce soit ZStack ou .overlay en ce moment.

La hiérarchie des vues est presque identique - selon Xcode.

struct ContentView: View {

    var body: some View {

        VStack {
            Circle().fill(Color.red)
                .overlay(Circle().stroke(Color.blue))
            ZStack {
                 Circle().fill(Color.red)
                 Circle().stroke(Color.blue)
            }
        }

    }

}

Sortie :

enter image description here


Afficher la hiérarchie :

enter image description here

4
Matteo Pacini

Pour référence future, la solution de @ Imran fonctionne, mais vous devez également prendre en compte la largeur de trait dans votre cadre total par rembourrage:

struct Foo: View {
    private let lineWidth: CGFloat = 12
    var body: some View {
        Circle()
            .stroke(Color.purple, lineWidth: self.lineWidth)
        .overlay(
            Circle()
                .fill(Color.yellow)
        )
        .padding(self.lineWidth)
    }
}

enter image description here

2
manman

J'ai assemblé le wrapper suivant sur la base des réponses ci-dessus. Cela rend cela un peu plus facile et le code un peu plus simple à lire.

struct FillAndStroke<Content:Shape> : View
{
  let fill : Color
  let stroke : Color
  let content : () -> Content

  init(fill : Color, stroke : Color, @ViewBuilder content : @escaping () -> Content)
  {
    self.fill = fill
    self.stroke = stroke
    self.content = content
  }

  var body : some View
  {
    ZStack
    {
      content().fill(self.fill)
      content().stroke(self.stroke)
    }
  }
}

Il peut être utilisé comme ceci:

FillAndStroke(fill : Color.red, stroke : Color.yellow)
{
  Circle()
}

Espérons que Apple trouvera un moyen de prendre en charge à la fois le remplissage et le contour d'une forme à l'avenir.

1
jensrodi

Ma solution:

import SwiftUI

extension Shape {
    /// fills and strokes a shape
    public func fill<S:ShapeStyle>(
        _ fillContent: S, 
        stroke       : StrokeStyle
    ) -> some View {
        ZStack {
            self.fill(fillContent)
            self.stroke(style:stroke)
        }
    }
}

Exemple:


struct ContentView: View {
    // fill gradient
    let gradient = RadialGradient(
        gradient   : Gradient(colors: [.yellow, .red]), 
        center     : UnitPoint(x: 0.25, y: 0.25), 
        startRadius: 0.2, 
        endRadius  : 200
    )
    // stroke line width, dash
    let w: CGFloat   = 6       
    let d: [CGFloat] = [20,10]
    // view body
    var body: some View {
        HStack {
            Circle()
                // ⭐️ Shape.fill(_:stroke:)
                .fill(Color.red, stroke: StrokeStyle(lineWidth:w, dash:d))
            Circle()
                .fill(gradient, stroke: StrokeStyle(lineWidth:w, dash:d))
        }.padding().frame(height: 300)
    }
}

Résultat:

fill and stroke circles

1
lochiwei

S'appuyant sur la réponse précédente de lochiwei ...

public func fill<S:ShapeStyle>(_ fillContent: S,
                                   opacity: Double,
                                   strokeWidth: CGFloat,
                                   strokeColor: S) -> some View
    {
        ZStack {
            self.fill(fillContent).opacity(opacity)
            self.stroke(strokeColor, lineWidth: strokeWidth)
        }
    }

Utilisé sur un objet Shape:

struct SelectionIndicator : Shape {
    let parentWidth: CGFloat
    let parentHeight: CGFloat
    let radius: CGFloat
    let sectorAngle: Double


    func path(in rect: CGRect) -> Path { ... }
}

SelectionIndicator(parentWidth: g.size.width,
                        parentHeight: g.size.height,
                        radius: self.radius + 10,
                        sectorAngle: self.pathNodes[0].sectorAngle.degrees)
                    .fill(Color.yellow, opacity: 0.2, strokeWidth: 3, strokeColor: Color.white)
0
user6902806

Si nous voulons avoir un cercle avec un effet de bordure no moved Comme nous pouvons le voir en utilisant ZStack { Circle().fill(), Circle().stroke } j'ai préparé quelque chose comme ci-dessous:

Premier raide

Nous créons un nouveau shape

struct CircleShape: Shape {

    // MARK: - Variables
    var radius: CGFloat

    func path(in rect: CGRect) -> Path {
        let centerX: CGFloat = rect.width / 2
        let centerY: CGFloat = rect.height / 2
        var path = Path()
        path.addArc(center: CGPoint(x: centerX, y: centerY), radius: radius, startAngle: Angle(degrees: .zero)
            , endAngle: Angle(degrees: 360), clockwise: true)

        return path
    }
}

Deuxième raide

Nous créons un nouveau ButtonStyle

struct LikeButtonStyle: ButtonStyle {

        // MARK: Constants
        private struct Const {
            static let yHeartOffset: CGFloat = 1
            static let pressedScale: CGFloat = 0.8
            static let borderWidth: CGFloat = 1
        }

        // MARK: - Variables
        var radius: CGFloat
        var isSelected: Bool

        func makeBody(configuration: Self.Configuration) -> some View {
            ZStack {
                if isSelected {
                    CircleShape(radius: radius)
                        .stroke(Color.red)
                        .animation(.easeOut)
                }
                CircleShape(radius: radius - Const.borderWidth)
                    .fill(Color.white)
                configuration.label
                    .offset(x: .zero, y: Const.yHeartOffset)
                    .foregroundColor(Color.red)
                    .scaleEffect(configuration.isPressed ? Const.pressedScale : 1.0)
            }
        }
    }

Dernière pente

Nous créons un nouveau View

struct LikeButtonView: View {

    // MARK: - Typealias
    typealias LikeButtonCompletion = (Bool) -> Void

    // MARK: - Constants
    private struct Const {
        static let selectedImage = Image(systemName: "heart.fill")
        static let unselectedImage = Image(systemName: "heart")
        static let textMultiplier: CGFloat = 0.57
        static var textSize: CGFloat { 30 * textMultiplier }
    }

    // MARK: - Variables
    @State var isSelected: Bool = false
    private var radius: CGFloat = 15.0
    private var completion: LikeButtonCompletion?

    init(isSelected: Bool, completion: LikeButtonCompletion? = nil) {
        _isSelected = State(initialValue: isSelected)
        self.completion = completion
    }

    var body: some View {
        ZStack {
            Button(action: {
                withAnimation {
                    self.isSelected.toggle()
                    self.completion?(self.isSelected)
                }
            }, label: {
                setIcon()
                    .font(Font.system(size: Const.textSize))

            })
                .buttonStyle(LikeButtonStyle(radius: radius, isSelected: isSelected))
        }
    }

    // MARK: - Private methods
    private func setIcon() -> some View {
        isSelected ? Const.selectedImage : Const.unselectedImage
    }
}

Sortie: état sélectionné et non sélectionné

enter image description here

enter image description here

0
PiterPan