Круглые Конкретные Углы SwiftUI

Я знаю, что вы можете использовать .cornerRadius() закруглить все углы представления swiftUI, но есть ли способ закруглить только определенные углы, такие как верх?

12 ответов

Решение

Вы можете использовать форму для достижения этого. Вот пример:

Я создал вид, что вы можете использовать автономный для рисования формы, или вы можете передать его .background(RoundedCorners(...)),

struct ContentView : View {
    var body: some View {

        Text("Hello World!")
            .foregroundColor(.white)
            .font(.largeTitle)
            .padding(20)
            .background(RoundedCorners(color: .blue, tl: 0, tr: 30, bl: 30, br: 0))
    }
}
struct RoundedCorners: View {
    var color: Color = .blue
    var tl: CGFloat = 0.0
    var tr: CGFloat = 0.0
    var bl: CGFloat = 0.0
    var br: CGFloat = 0.0

    var body: some View {
        GeometryReader { geometry in
            Path { path in

                let w = geometry.size.width
                let h = geometry.size.height

                // Make sure we do not exceed the size of the rectangle
                let tr = min(min(self.tr, h/2), w/2)
                let tl = min(min(self.tl, h/2), w/2)
                let bl = min(min(self.bl, h/2), w/2)
                let br = min(min(self.br, h/2), w/2)

                path.move(to: CGPoint(x: w / 2.0, y: 0))
                path.addLine(to: CGPoint(x: w - tr, y: 0))
                path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false)
                path.addLine(to: CGPoint(x: w, y: h - br))
                path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
                path.addLine(to: CGPoint(x: bl, y: h))
                path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
                path.addLine(to: CGPoint(x: 0, y: tl))
                path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)
            }
            .fill(self.color)
        }
    }
}

ОБНОВИТЬ

Так как я опубликовал ответ, я написал статью с подробным объяснением того, как работает GeometryReader (он включает в себя пример этого вопроса). Если вам нужно расширить свои знания о GeometryReader, перейдите по ссылке https://swiftui-lab.com/geometryreader-to-the-rescue/

Использование в качестве настраиваемого модификатора

Вы можете использовать его как обычный модификатор:

.cornerRadius(20, corners: [.topLeft, .bottomRight])

Только если вы реализуете простое расширение на View нравится:

extension View {
    func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
        clipShape( RoundedCorner(radius: radius, corners: corners) )
    }
}

И вот структура, стоящая за этим:

struct RoundedCorner: Shape {

    var radius: CGFloat = .infinity
    var corners: UIRectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        return Path(path.cgPath)
    }
}

Вы также можете использовать фигуру как обтравочную маску.

Модификаторы просмотра упростили задачу:

struct CornerRadiusStyle: ViewModifier {
    var radius: CGFloat
    var corners: UIRectCorner

    struct CornerRadiusShape: Shape {

        var radius = CGFloat.infinity
        var corners = UIRectCorner.allCorners

        func path(in rect: CGRect) -> Path {
            let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
            return Path(path.cgPath)
        }
    }

    func body(content: Content) -> some View {
        content
            .clipShape(CornerRadiusShape(radius: radius, corners: corners))
    }
}

extension View {
    func cornerRadius(radius: CGFloat, corners: UIRectCorner) -> some View {
        ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
    }
}

Пример:

//left Button
.cornerRadius(radius: 6, corners: [.topLeft, .bottomLeft])

//right Button
.cornerRadius(radius: 6, corners: [.topRight, .bottomRight])

Я обнаружил простой способ скругления односторонних углов. Он использует «танец положительных и отрицательных дополнений», чтобы выполнить именно то, что я искал.

Итак, в основном это работает так:

  1. Добавьте немного отступа в нижней части вашего представления
  2. Закруглите все углы с помощью .cornerRadius(_:)
  3. Удалите дополнение, применив отрицательное дополнение того же значения
      struct OnlyTopRoundedCornersDemo: View {
    let radius = 12 // radius we need
    var body: some View {
        Rectangle()
            .frame(height: 50)
            .foregroundColor(.black)
        .padding(.bottom, radius)
        .cornerRadius(radius)
        .padding(.bottom, -radius)
    }
}

Результирующий вид выглядит следующим образом:

Как видите, его рамка идеально выровнена с содержимым (синяя рамка). Тот же подход можно использовать для скругления пар нижних или боковых углов. Надеюсь, это кому-нибудь поможет!

Шаг 1: Создайте фигуру, которая может обрезать вид. Мы собираемся использовать UIBeizerPath для реализации скругления определенного угла. Затем скопируйте cgPath в Path.

      //step 1 -- Create a shape view which can give shape

    struct CornerRadiusShape: Shape {
        var radius = CGFloat.infinity
        var corners = UIRectCorner.allCorners
    
        func path(in rect: CGRect) -> Path {
            let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
            return Path(path.cgPath)
        }
    }

Шаг 2. Вставьте фигуру в ViewModifier

      //step 2 - embed shape in viewModifier to help use with ease

    struct CornerRadiusStyle: ViewModifier {
        var radius: CGFloat
        var corners: UIRectCorner
    
        func body(content: Content) -> some View {
            content
                .clipShape(CornerRadiusShape(radius: radius, corners: corners))
        }
    }

Шаг 3: Добавьте полиморфную функцию с подписью CornerRadius.

      //step 3 - crate a polymorphic view with same name as swiftUI's cornerRadius

extension View {
    func cornerRadius(radius: CGFloat, corners: UIRectCorner) -> some View {
        ModifiedContent(content: self, modifier: CornerRadiusStyle(radius: radius, corners: corners))
    }
}

Шаг 4: Используйте следующее:

      //use any way you want
struct ContentView: View {
    var body: some View {
        VStack {
            Rectangle()
                .frame(width: 100, height: 100, alignment: .center)
                .cornerRadius(radius: 20.0, corners: [.topLeft])
            
            Rectangle()
                .frame(width: 100, height: 100, alignment: .center)
                .cornerRadius(radius: 20.0, corners: [.topLeft, .bottomLeft])
            
            Rectangle()
                .frame(width: 100, height: 100, alignment: .center)
                .cornerRadius(radius: 20.0, corners: [.allCorners])
            
        }
    }
}

Сделанный! :)

Другой вариант (может быть, лучше) на самом деле вернуться к UIKIt для этого. Например:

struct ButtonBackgroundShape: Shape {

    var cornerRadius: CGFloat
    var style: RoundedCornerStyle

    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
        return Path(path.cgPath)
    }
}

Вот адаптация для macOS:

      // defines OptionSet, which corners to be rounded – same as UIRectCorner
struct RectCorner: OptionSet {
    
    let rawValue: Int
        
    static let topLeft = RectCorner(rawValue: 1 << 0)
    static let topRight = RectCorner(rawValue: 1 << 1)
    static let bottomRight = RectCorner(rawValue: 1 << 2)
    static let bottomLeft = RectCorner(rawValue: 1 << 3)
    
    static let allCorners: RectCorner = [.topLeft, topRight, .bottomLeft, .bottomRight]
}


// draws shape with specified rounded corners applying corner radius
struct RoundedCornersShape: Shape {
    
    var radius: CGFloat = .zero
    var corners: RectCorner = .allCorners

    func path(in rect: CGRect) -> Path {
        var path = Path()

        let p1 = CGPoint(x: rect.minX, y: corners.contains(.topLeft) ? rect.minY + radius  : rect.minY )
        let p2 = CGPoint(x: corners.contains(.topLeft) ? rect.minX + radius : rect.minX, y: rect.minY )

        let p3 = CGPoint(x: corners.contains(.topRight) ? rect.maxX - radius : rect.maxX, y: rect.minY )
        let p4 = CGPoint(x: rect.maxX, y: corners.contains(.topRight) ? rect.minY + radius  : rect.minY )

        let p5 = CGPoint(x: rect.maxX, y: corners.contains(.bottomRight) ? rect.maxY - radius : rect.maxY )
        let p6 = CGPoint(x: corners.contains(.bottomRight) ? rect.maxX - radius : rect.maxX, y: rect.maxY )

        let p7 = CGPoint(x: corners.contains(.bottomLeft) ? rect.minX + radius : rect.minX, y: rect.maxY )
        let p8 = CGPoint(x: rect.minX, y: corners.contains(.bottomLeft) ? rect.maxY - radius : rect.maxY )

        
        path.move(to: p1)
        path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.minY),
                    tangent2End: p2,
                    radius: radius)
        path.addLine(to: p3)
        path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.minY),
                    tangent2End: p4,
                    radius: radius)
        path.addLine(to: p5)
        path.addArc(tangent1End: CGPoint(x: rect.maxX, y: rect.maxY),
                    tangent2End: p6,
                    radius: radius)
        path.addLine(to: p7)
        path.addArc(tangent1End: CGPoint(x: rect.minX, y: rect.maxY),
                    tangent2End: p8,
                    radius: radius)
        path.closeSubpath()

        return path
    }
}

// View extension, to be used like modifier:
// SomeView().roundedCorners(radius: 20, corners: [.topLeft, .bottomRight])
extension View {
    func roundedCorners(radius: CGFloat, corners: RectCorner) -> some View {
        clipShape( RoundedCornersShape(radius: radius, corners: corners) )
    }
}

iOS 16+

просто используйте UnevenRoundedRectangle

       VStack {
    UnevenRoundedRectangle(cornerRadii: .init(bottomTrailing: 50, topTrailing: 50))
                    .fill(.orange)
                    .frame(width: 200, height: 100)
 }

Результат:

Еще один вариант в топ самых чистых (iOS 15+):

      .background(Color.orange, in: RoundedRectangle(cornerRadius: 20))
.background(content: { Color.white.padding(.top, 20) })

Хочу добавить к ответу Контики;

Если вы используете вариант 2 и хотите добавить обводку к фигуре, обязательно добавьте следующее прямо перед возвратом пути:

      path.addLine(to: CGPoint(x: w/2.0, y: 0))

В противном случае обводка будет прервана от верхнего левого угла до середины верхней стороны.

Используйте код в этой ссылке, где структура формы типа RoundedCorners:Shape{//} /questions/50160132/kruglyie-konkretnyie-uglyi-swiftui/50160142#50160142

используйте ниже строки кода в этой ссылке, прежде чемpath.closeSubpath()

      path.move(to: CGPoint(x: 280, y: 20))
path.addLine(to: CGPoint(x: w, y: 0))
path.addArc(center: CGPoint(x: w, y: 70), radius: br,   //x =  move triangle to right left
startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180), clockwise: false)

используйте этот пакет, модификатор:.cornerRadius(,corners:)

Другие вопросы по тегам