How to make the lower right corner sharp ? SwiftUI - ios

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())
}
}

Related

How to rotate SwiftUI Path around its center?

I have the following simple SwiftUI Path of an arrow which I wish to rotate around it's center:
#State var heading: Double = 0.0
#State var xOffset = 0.0
#State var yOffset = 0.0
var body: some View {
TabView {
NavigationStack {
Path { path in
path.move(to: CGPoint(x: xOffset, y: yOffset))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset + 20))
path.addLine(to: CGPoint(x: xOffset + 50, y: yOffset + 0))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset - 20))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset + 0))
}
.stroke(lineWidth: 3)
.foregroundColor(.red)
.rotationEffect(Angle(degrees: heading), anchor: .center)
.transformEffect(CGAffineTransform(translationX: 80, y: 80))
Slider(value: $heading, in: 0...360, step: 30)
}
}
}
It does however not rotate around its center:
How can I rotate a Path around its center (or around any other relative point on its bounds)?
The Path view greedily takes up all of the space available to it, so the view is much larger than you think, and its center is far away. So when you rotate the path, it moves off the screen. You can see this by adding .background(Color.yellow) to the Path view.
It's easier to manage the rotation of the arrow if you make it an .overlay of another View (Color.clear) and then rotate that View. You can make the view visible by using Color.yellow while tuning, and then position the arrow relative to its parent view. The nice thing about this is that when you rotate the parent view, the arrow will stick to it and rotate predictably.
struct ContentView: View {
#State var heading: Double = 0.0
var body: some View {
TabView {
NavigationStack {
Color.clear
.frame(width: 60, height: 60)
.overlay (
Path { path in
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 0, y: 20))
path.addLine(to: CGPoint(x: 50, y: 0))
path.addLine(to: CGPoint(x: 0, y: -20))
path.addLine(to: CGPoint(x: 0, y: 0))
}
.stroke(lineWidth: 3)
.foregroundColor(.red)
.offset(x: 5, y: 30)
)
.rotationEffect(Angle(degrees: heading), anchor: .center)
Slider(value: $heading, in: 0...360, step: 30)
}
}
}
}
How do I make my code work?
You need to give your Path view a reasonable sized frame. Here I added .frame(width: 60, height: 60) which is big enough to hold your arrow path.
Since you have defined xOffset and yOffset, use them to move the path drawing within its view. Temporarily add a background to your path view with .background(Color.yellow) and then adjust xOffset and yOffset until your arrow is drawing inside of that view. You can then remove this .background.
This has the same effect as the overlay method presented above:
struct ContentView: View {
#State var heading: Double = 0.0
#State var xOffset = 5.0 // here
#State var yOffset = 30.0 // here
var body: some View {
TabView {
NavigationStack {
Path { path in
path.move(to: CGPoint(x: xOffset, y: yOffset))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset + 20))
path.addLine(to: CGPoint(x: xOffset + 50, y: yOffset + 0))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset - 20))
path.addLine(to: CGPoint(x: xOffset + 0, y: yOffset + 0))
}
.stroke(lineWidth: 3)
.foregroundColor(.red)
// .background(Color.yellow)
.frame(width: 60, height: 60) // here
.rotationEffect(Angle(degrees: heading), anchor: .center)
//.transformEffect(CGAffineTransform(translationX: 80, y: 80))
Slider(value: $heading, in: 0...360, step: 30)
}
}
}
}

SwiftUI draw a file icon

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)

Flip a SwiftUI Shape based on layout direction

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()
}
}

Make two buttons acting as one SwiftUI

I want to make two buttons acting as one, or have a single button but with the touch area only the zone that is red like in the picture. A normal button would also have on the touch area that white space on which I put the x-es, which I don't want.
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Spacer()
Button(action: {}) {
Text("This is button 1")
.frame(width: 100, height: 200)
.background(Color.red)
}
Button(action: {}) {
Text("This is button 2")
.frame(height: 100)
.background(Color.red)
}
Spacer()
}
}
}
Here's one way it could be done. Have a single button with two overlaid white rectangles. Only the red area can be clicked:
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Spacer()
Button(action: {}) {
Text("This is one big button")
.frame(width: 200, height: 200)
.background(Color.red)
}
.overlay(
HStack {
Spacer()
VStack {
Rectangle()
.frame(width: 100, height: 50)
.foregroundColor(.white)
Spacer()
Rectangle()
.frame(width: 100, height: 50)
.foregroundColor(.white)
}
}
)
Spacer()
}
}
}
Solution using a custom Shape
Another way to do it would be to create a custom Shape for the Button, and then use it to draw and set its contentShape():
struct ButtonShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let width = rect.width
let height = rect.height
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: width/2, y: 0))
path.addLine(to: CGPoint(x: width/2, y: height/4))
path.addLine(to: CGPoint(x: width, y: height/4))
path.addLine(to: CGPoint(x: width, y: 3 * height/4))
path.addLine(to: CGPoint(x: width/2, y: 3 * height/4))
path.addLine(to: CGPoint(x: width/2, y: height))
path.addLine(to: CGPoint(x: 0, y: height))
path.closeSubpath()
return path
}
}
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Spacer()
Button(action: {}) {
Color.red
.clipShape(ButtonShape())
.overlay(
Text("This is one big button")
)
}
.contentShape(ButtonShape())
.frame(width: 200, height: 200)
Spacer()
}
}
}
This solution overall works better because the clipped areas aren't drawn making it easier to put this button on a colored background for instance.

SwiftUI - create a single dashed line with SwiftUI

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()
}
}

Resources