Last active
December 8, 2023 11:55
-
-
Save Koshimizu-Takehito/d1ae44abad1fe66d46b757863a18ed4b to your computer and use it in GitHub Desktop.
SwiftUIのViewに角丸な枠線を引くサンプルコード
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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