Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Last active December 8, 2023 11:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Koshimizu-Takehito/d1ae44abad1fe66d46b757863a18ed4b to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/d1ae44abad1fe66d46b757863a18ed4b to your computer and use it in GitHub Desktop.
SwiftUIのViewに角丸な枠線を引くサンプルコード
import SwiftUI
struct ContentView: View {
var radius: CGFloat = 40
var width: CGFloat = 20
var body: some View {
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 40) {
HelloWorld()
.borderedMask(Circle(), style: .mint, width: width)
HelloWorld()
.roundedBorder(.mint, width: width, radius: radius)
HelloWorld()
.roundedBorder(.mint, width: width, radius: radius, corner: [.minXminY, .maxXmaxY])
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}
struct HelloWorld: View {
var body: some View {
Text("Hello,\nWorld!")
.font(.title)
.fontWeight(.bold)
.lineLimit(2)
.multilineTextAlignment(.center)
.padding(60)
.background(.orange)
}
}
extension View {
func roundedBorder<S: ShapeStyle>(_ style: S, width: CGFloat, radius: CGFloat) -> some View {
modifier(RoundedBorderModifier(style: style, width: width, radius: radius))
}
func roundedBorder<S: ShapeStyle>(_ style: S, width: CGFloat, radius: CGFloat, corner: RectCorner) -> some View {
borderedMask(PartialRoundedRectangle(cornerRadius: radius, corner: corner), style: style, width: width)
}
func borderedMask<Shape: SwiftUI.Shape, Style: ShapeStyle>(_ shape: Shape, style: Style, width: CGFloat) -> some View {
modifier(BorderedMaskModifier(shape: shape, style: style, width: width))
}
}
struct RoundedBorderModifier<Style: ShapeStyle>: ViewModifier {
var style: Style, width: CGFloat = 0, radius: CGFloat
func body(content: Content) -> some View {
content
.overlay {
RoundedRectangle(cornerRadius: radius)
.stroke(lineWidth: width*2)
.fill(style)
}
.mask {
RoundedRectangle(cornerRadius: radius)
}
}
}
struct BorderedMaskModifier<Shape: SwiftUI.Shape, Style: ShapeStyle>: ViewModifier {
var shape: Shape, style: Style, width: CGFloat = 0
func body(content: Content) -> some View {
content
.overlay {
shape
.stroke(lineWidth: width*2)
.fill(style)
}
.mask {
shape
}
}
}
struct PartialRoundedRectangle: Shape {
var minXminY: CGFloat = 0
var maxXminY: CGFloat = 0
var maxXmaxY: CGFloat = 0
var minXmaxY: CGFloat = 0
func path(in rect: CGRect) -> Path {
let start = CGPoint(x: rect.minX + minXminY, y: rect.minY)
let points: [CGPoint] = [
CGPoint(x: rect.maxX - maxXminY, y: rect.minY),
CGPoint(x: rect.maxX, y: rect.maxY - maxXmaxY),
CGPoint(x: rect.minX + minXmaxY, y: rect.maxY),
CGPoint(x: rect.minX, y: rect.minY + minXminY)
]
let centers: [CGPoint] = [
CGPoint(x: rect.maxX - maxXminY, y: rect.minY + maxXminY),
CGPoint(x: rect.maxX - maxXmaxY, y: rect.maxY - maxXmaxY),
CGPoint(x: rect.minX + minXmaxY, y: rect.maxY - minXmaxY),
CGPoint(x: rect.minX + minXminY, y: rect.minY + minXminY)
]
let radiuses: [CGFloat] = [
maxXminY,
maxXmaxY,
minXmaxY,
minXminY
]
let angles: [Angle] = [
.radians(.pi*3/2),
.radians(0),
.radians(.pi/2),
.radians(.pi)
]
var path = Path()
path.move(to: start)
path.addLine(to: points[0])
path.addArc(center: centers[0], radius: radiuses[0], startAngle: angles[0], endAngle: angles[1], clockwise: false)
path.addLine(to: points[1])
path.addArc(center: centers[1], radius: radiuses[1], startAngle: angles[1], endAngle: angles[2], clockwise: false)
path.addLine(to: points[2])
path.addArc(center: centers[2], radius: radiuses[2], startAngle: angles[2], endAngle: angles[3], clockwise: false)
path.addLine(to: points[3])
path.addArc(center: centers[3], radius: radiuses[3], startAngle: angles[3], endAngle: angles[0], clockwise: false)
path.closeSubpath()
return path
}
}
struct RectCorner: OptionSet {
var rawValue: Int
static let minXminY = Self.init(rawValue: 1 << 0)
static let maxXminY = Self.init(rawValue: 1 << 1)
static let maxXmaxY = Self.init(rawValue: 1 << 2)
static let minXmaxY = Self.init(rawValue: 1 << 3)
}
extension PartialRoundedRectangle {
init(cornerRadius radius: CGFloat, corner: RectCorner) {
self.init(
minXminY: corner.contains(.minXminY) ? radius : .zero,
maxXminY: corner.contains(.maxXminY) ? radius : .zero,
maxXmaxY: corner.contains(.maxXmaxY) ? radius : .zero,
minXmaxY: corner.contains(.minXmaxY) ? radius : .zero
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewInterfaceOrientation(.portrait)
.preferredColorScheme(.light)
.environment(\.sizeCategory, .medium)
.previewLayout(.device)
.previewDevice("iPhone 13 mini")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment