I've made a SemiRoundedRectangle shape which I'm using to clipShape a side menu. I need to flip it if the user is in an RTL language, but not sure the best way of achieving this.
I've tried .flipsForRightToLeftLayoutDirection(true) but this flips the actual Arabic text too. When I try rotating the shape it no longer conforms to the Shape protocol and so I can no longer use it in .clipShape. Everything else in SwiftUI just magically flips itself when I switch into Arabic, is there something I could add to my shape to give it these magic powers too?
Thanks for your help :)
import SwiftUI
struct SemiRoundedRectangle: Shape {
var cornerRadius: CGFloat
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX+cornerRadius, y: rect.maxY))
path.addArc(center: CGPoint(x: cornerRadius, y: rect.height - cornerRadius),
radius: cornerRadius,
startAngle: .degrees(90),
endAngle: .degrees(180), clockwise: false)
path.addLine(to: CGPoint(x: 0, y: cornerRadius))
path.addArc(center: CGPoint(x: cornerRadius, y: cornerRadius),
radius: cornerRadius,
startAngle: .degrees(180),
endAngle: .degrees(270), clockwise: false)
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
return path
}
}
struct TestView {
var body: some View {
HStack {
Text("ايه الأخبار؟")
.padding()
.background(Color.green)
.clipShape(SemiRoundedRectangle(cornerRadius: 10.0))
Spacer()
}
}
}
Try attaching the clipShape to the Color.green instead:
struct TestView: View {
var body: some View {
HStack {
Text("ايه الأخبار؟")
.padding()
.background(
Color.green /// here!
.clipShape(SemiRoundedRectangle(cornerRadius: 10.0))
.flipsForRightToLeftLayoutDirection(true)
)
Spacer()
}
}
}
Result:
English
RTL Language
There is a generic solution:
extension Shape {
func flipped(_ axis: Axis = .horizontal, anchor: UnitPoint = .center) -> ScaledShape<Self> {
switch axis {
case .horizontal:
return scale(x: -1, y: 1, anchor: anchor)
case .vertical:
return scale(x: 1, y: -1, anchor: anchor)
}
}
}
Use for this case:
struct TestView: View {
var body: some View {
HStack {
Text("ايه الأخبار؟")
.padding()
.background(Color.green)
.clipShape(SemiRoundedRectangle(cornerRadius: 20.0).flipped())
Spacer()
}
.padding()
}
}
Related
I would like to be able to get an image like this, with the word "File" in the center.
But I only managed to do this, you know how to give me a hand.
import SwiftUI
import CoreGraphics
struct File: Shape {
func path(in rect: CGRect) -> Path {
let topsx = CGPoint(x: 0, y: rect.minY)
let topdx = CGPoint(x: rect.maxX/1.5, y: 0)
let bottomsx = CGPoint(x: 0, y: rect.maxY)
let bottomdx = CGPoint(x: rect.maxX, y: rect.maxY)
var path = Path()
path.move(to: bottomdx)
path.addLine(to: bottomdx)
path.addLine(to: bottomsx)
path.addLine(to: topsx)
path.addLine(to: topdx)
return path
}
}
struct ContentView: View {
var body: some View {
ZStack {
File().fill(Color.black)
Text("File")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Personally I would not try to draw the icon with code.
Here's a simplified solution with a SF Symbol, but you can just find a nice document icon, download it, add it to your assets and use that instead.
ZStack {
Image(systemName: "doc.fill").resizable()
.scaledToFit()
.foregroundColor(.gray)
.overlay {
Text("File").font(.title).foregroundColor(.white).shadow(color: .black, radius: 10, x: 5, y: 5).offset(x:0, y: 20)
}
}.frame(height:200)
How to make the lower right corner sharp? the result is shown in the screenshot
here is my code now:
struct Test123: View {
var body: some View {
Text("Hello, World!")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.black)
}
}
The possible solution is by using .clipShape by preparing any custom shape you want.
Here a demo (prepared with Xcode 13 / iOS 15)
struct SharpShape: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
let chunk = rect.height * 0.5
path.move(to: .zero)
path.addLine(to: CGPoint(x: rect.width, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: chunk))
path.addLine(to: CGPoint(x: max(rect.width - chunk, rect.width / 2), y: rect.height))
path.addLine(to: CGPoint(x: 0, y: rect.height))
}
}
}
struct Test123: View {
var body: some View {
Text("Hello, World!")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.black)
.clipShape(SharpShape())
}
}
I have two paths I'm trying to animate in one continuous motion (one after the other). I'm drawing a circle and trying to follow that up with a line.
#State private var revealStroke = false
Path { path in
path.addArc(center: CGPoint(x: 100, y: 100), radius: CGFloat(50), startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: true)
path.addLines([CGPoint(x: 200, y: 100), CGPoint(x: 150, y: 100)])
}
.trim(from: revealStroke ? 0 : 1, to: 1)
.stroke(Color.purple, lineWidth: 3)
.animation(Animation.easeOut(duration: 3))
.onAppear() {
self.revealStroke.toggle()
}
You toggled trim in wrong way that should be done in to:
import SwiftUI
struct ContentView: View {
#State private var startDraw: Bool = Bool()
var body: some View {
VStack(spacing: 30.0) {
Path { path in
path.addArc(center: CGPoint(x: 100, y: 100), radius: 50.0, startAngle: Angle(degrees: 0.0), endAngle: Angle(degrees: 360.0), clockwise: true)
path.addLine(to: CGPoint(x: 200, y: 100))
}
.trim(from: 0.0, to: startDraw ? 1.0 : 0.0)
.stroke(style: StrokeStyle(lineWidth: 10.0, lineCap: .round, lineJoin: .round))
.shadow(color: Color.black.opacity(0.2), radius: 0.0, x: 20, y: 20)
.frame(width: 250, height: 200, alignment: .center)
.background(Color.yellow)
.foregroundColor(Color.purple)
.cornerRadius(10.0)
.animation(Animation.easeOut(duration: 3), value: startDraw)
Button("start") { startDraw.toggle() }.font(Font.body.bold())
}
.shadow(radius: 10.0)
}
}
I am trying to draw a circle in SwiftUI using a Shape, Geometry reader and Path, I want a shadow to sit right underneath the circle but the shadow is coming out offset and I can't seem to get it to draw where it should:
struct ContentView: View {
var body: some View {
return GeometryReader { geometry in
VStack(alignment: .center) {
BackgroundRing()
.stroke(Color.red, style: StrokeStyle(lineWidth: geometry.size.width < geometry.size.height ? geometry.size.width / 12.0 : geometry.size.height / 12))
.padding()
.shadow(color: .gray, radius: 1.0, x: 0.0, y: 0.0)
}
}
}
}
struct BackgroundRing : Shape {
func path(in rect: CGRect) -> Path {
var path: Path = Path()
let radiusOfRing: CGFloat = (rect.width < rect.height ? rect.width/2 - rect.width / 12 : rect.height/2 - rect.height / 12)
path.addRelativeArc(center: CGPoint(x: rect.width/2, y: rect.height/2),
radius: radiusOfRing,
startAngle: Angle(radians: 0.0),
delta: Angle(radians: Double.pi * 2.0 ))
return path
}
}
OK So I seem to have managed to fix the problem. There is something going on with the width / heigh that interacts with the code to calculate the location of the shadow - the shadow position seems to come from the frame dimensions rather than the Shape.
Adding
.aspectRatio(contentMode: .fit)
fixes the problem
Additionally, it seems that .shadow automatically sets the default offset to the same value as the radius, so to get a real offset of 0.0 you have to set it relative to the radius, like this:
.shadow(radius: 10.0, x: -10.0, y: -10.0)
Looks to me like a bug, but this work around solves it:
import SwiftUI
struct ContentView: View {
var body: some View {
return GeometryReader { geometry in
VStack(alignment: .center) {
BackgroundRing()
.stroke(Color.red,
style: StrokeStyle(lineWidth: geometry.size.width < geometry.size.height ? geometry.size.width / 12.0 : geometry.size.height / 12))
.shadow(radius: 30.0, x: -30.0, y: -30.0)
.aspectRatio(contentMode: .fit)
}
}
}
}
struct BackgroundRing : Shape {
func path(in rect: CGRect) -> Path {
var path: Path = Path()
let radiusOfRing: CGFloat = (rect.width < rect.height ? rect.width/2 - rect.width / 12 : rect.height/2 - rect.height / 12)
path.addRelativeArc(center: CGPoint(x: rect.width/2, y: rect.height/2), // <- this change solved the problem
radius: radiusOfRing,
startAngle: Angle(radians: 0.0),
delta: Angle(radians: Double.pi * 2.0 ))
return path
}
}
I need to create a single dashed line. I tried going about it by creating a Rectangle view with a dashed stroke. However, when setting the height of the rectangle to 1, it results in a double line as its showing both the top and bottom borders of the view.
This is the code:
Rectangle()
.fill(Color.clear)
.frame(height: 1, alignment: .bottom)
.overlay(
RoundedRectangle(cornerRadius: 0)
.stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
.foregroundColor(Color(UIColor.blue))
)
Change the dash value to increase or decrease the number of dash in the line.
struct ContentView: View {
var body: some View {
Line()
.stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
.frame(height: 1)
}
}
struct Line: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
return path
}
}
Result:
Depending on what you want to do, you can do something like this:
VStack {
Path{ path in
path.move(to: CGPoint(x: 20, y: 300))
path.addLine(to: CGPoint(x: 200, y: 300))
}
.stroke(style: StrokeStyle( lineWidth: 10, dash: [5]))
.foregroundColor(Color(UIColor.blue))
}
You will get something like this:
Improved #kazi.munshimun solution. Vetical line and horizontal line:
struct VLine: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
}
}
}
struct HLine: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.midY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
}
}
}
Usage:
VLine().stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
HLine().stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
Here is an ultimate way for you to add and draw lines with more easier options:
struct CustomLineShapeWithAlignment: Shape {
let stratPoint: Alignment
let endPoint: Alignment
init(stratPoint: Alignment, endPoint: Alignment) {
self.stratPoint = stratPoint
self.endPoint = endPoint
}
private func cgPointTranslator(alignment: Alignment, rect: CGRect) -> CGPoint {
switch alignment {
case .topLeading: return CGPoint(x: rect.minX, y: rect.minY)
case .top: return CGPoint(x: rect.midX, y: rect.minY)
case .topTrailing: return CGPoint(x: rect.maxX, y: rect.minY)
case .leading: return CGPoint(x: rect.minX, y: rect.midY)
case .center: return CGPoint(x: rect.midX, y: rect.midY)
case .trailing: return CGPoint(x: rect.maxX, y: rect.midY)
case .bottomLeading: return CGPoint(x: rect.minX, y: rect.maxY)
case .bottom: return CGPoint(x: rect.midX, y: rect.maxY)
case .bottomTrailing: return CGPoint(x: rect.maxX, y: rect.maxY)
default: return CGPoint(x: rect.minX, y: rect.minY)
}
}
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: cgPointTranslator(alignment: stratPoint, rect: rect))
path.addLine(to: cgPointTranslator(alignment: endPoint, rect: rect))
}
}
}
use case:
struct ContentView: View {
var body: some View {
CustomLineShapeWithAlignment(stratPoint: .top, endPoint: .bottom)
.stroke(style: StrokeStyle(lineWidth: 1.0, dash: [5]))
.background(Color.red)
ZStack {
CustomLineShapeWithAlignment(stratPoint: .top, endPoint: .bottom)
.stroke(style: StrokeStyle(lineWidth: 1.0, dash: [5]))
.frame(width: 1.0)
CustomLineShapeWithAlignment(stratPoint: .leading, endPoint: .trailing)
.stroke(style: StrokeStyle(lineWidth: 1.0, dash: [5]))
.frame(height: 1.0)
}
.background(Color.gray)
CustomLineShapeWithAlignment(stratPoint: .topLeading, endPoint: .bottomTrailing)
.stroke(style: StrokeStyle(lineWidth: 1.0, dash: [5]))
.background(Color.blue)
}
}
result:
import SwiftUI
public struct DashedDivider: View {
private let overlayColor: Color
public init(_ overlayColor: Color = Color(UIColor.systemGray)) {
self.overlayColor = overlayColor
}
public var body: some View {
HLine()
.stroke(style: StrokeStyle(lineWidth: 1, dash: [5]))
.foregroundColor(overlayColor)
.frame(height: 1)
}
}
struct HLine: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
return path
}
}
struct DashedDivider_Previews: PreviewProvider {
static var previews: some View {
DashedDivider()
}
}