Круглые Конкретные Углы 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])
Я обнаружил простой способ скругления односторонних углов. Он использует «танец положительных и отрицательных дополнений», чтобы выполнить именно то, что я искал.
Итак, в основном это работает так:
- Добавьте немного отступа в нижней части вашего представления
- Закруглите все углы с помощью
.cornerRadius(_:)
- Удалите дополнение, применив отрицательное дополнение того же значения
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)