I want to make a bar containing mutual data of two users. I want it to be like this image:
I also used the Rectangle() shape here. But the bar starts to fill up after 0.5, it seems empty before that. What should I do here?
The code I wrote is as follows:
HStack{
ZStack(alignment: .leading){
Rectangle()
.trim(from:0.1, to:0.6)
.fill(Color.init( red: 0.965, green: 0.224, blue: 0.49))
.frame(width: 55, height: 11)
Text(String(nowduelList.user1StepCount))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
ZStack(alignment: .trailing){
Rectangle()
.trim(from:0, to: 0.8)
.fill(Color.init( red: 0.208, green: 0.231, blue: 0.314))
.frame(width: 55, height: 11)
Text(String(nowduelList.user2StepCount))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
}
And this is an image of my View:
You can rotate the first view to achieve this:
HStack(spacing: -5) {
ZStack(alignment: .leading){
Rectangle()
.trim(from:0, to:0.8) // << Same as second view
.rotation(Angle(degrees: 180)) // << Rotation
.fill(Color.init( red: 0.965, green: 0.224, blue: 0.49))
.frame(width: 55, height: 11)
Text(String(951))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
ZStack(alignment: .trailing){
Rectangle()
.trim(from:0, to: 0.8)
.fill(Color.init( red: 0.208, green: 0.231, blue: 0.314))
.frame(width: 55, height: 11)
Text(String(231))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
}
Here is the full code you can modify and optimize the code as you want.
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX/1.2, y: 0))
return path
}
}
struct LeadingTriangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.maxX/1.2-rect.maxX, y: 0))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: 0))
return path
}
}
var body: some View {
HStack{
ZStack(alignment: .leading){
Rectangle()
.fill(Color.white)
.frame(width: 150, height: 30)
.overlay(
Triangle()
.fill(Color.red)
)
Text(String(23.32))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
ZStack(alignment: .trailing){
Rectangle()
.fill(Color.white)
.frame(width: 150, height: 30)
.overlay(
LeadingTriangle()
.fill(Color.blue)
)
Text(String(23.32))
.font(.system(size: 8))
.bold()
.foregroundColor(Color.white)
}
}
}
Related
I am trying to split a rectangle into two sides with a slanted divider with background web images like this:
I tried using a triangle but having trouble dividing it in the correct direction and angle. Below is my working code I'm trying:
struct ContentView: View {
var body: some View {
VStack {
VStack {
Text("VS")
.bold()
.padding(4)
.background(.thinMaterial.opacity(0.8))
.clipShape(Circle())
}
.frame(maxWidth: .infinity, minHeight: 100)
.padding()
.background(
AsyncImage(url: URL(string: "https://picsum.photos/id/1037/1600/400"), transaction: Transaction(animation: .easeInOut)) { phase in
switch phase {
case .empty:
ProgressView()
case let .success(image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity)
.transition(.opacity)
default:
Color.red
.transition(.opacity)
}
}
.overlay(
AsyncImage(url: URL(string: "https://picsum.photos/id/1041/1600/400"), transaction: Transaction(animation: .easeInOut)) { phase in
switch phase {
case .empty:
ProgressView()
case let .success(image):
image
.resizable()
.scaledToFill()
.frame(maxWidth: .infinity)
.transition(.opacity)
default:
Color.blue
.transition(.opacity)
}
}
.clipShape(Triangle())
)
)
.cornerRadius(4)
}
.padding()
}
}
struct Triangle: Shape {
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.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
return path
}
}
This is what it looks like:
How can I accomplish the "back-slash" divider like in my first screenshot?
You just need to adjust the points in your Triangle to fit the shape that you desire. If you look at each point you currently have, you'll see that it matches the shape that it draws.
By adjusting a couple of the points, you can get the desired result:
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.maxX * 0.33, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX * 0.66, y: rect.maxY))
return path
}
}
I'm trying to make such ruler in SwiftUI https://imgur.com/a/M89GXsj. I wonder is it possible to do it with only one ForEach? Is it right implementation?
struct Ruler: View {
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(1...20, id: \.self) { num in
HStack(spacing: 40) {
HStack(alignment: .top, spacing: 3) {
Divider()
.frame(width: 1.5, height: 40)
.background(Color.white)
Text("\(num)")
.foregroundColor(Color.white)
.font(.system(size: 12, weight: .bold))
}
ForEach(1...3, id: \.self) { _ in
Divider()
.frame(width: 1.5, height: 15)
.background(Color.white)
.padding(.bottom, -14)
}
Spacer()
}
}
}
.frame(height: 40)
.background(Color.black)
}
}
struct Ruler_Previews: PreviewProvider {
static var previews: some View {
Ruler()
.previewLayout(.sizeThatFits)
}}
My recommendation would be to have each portion of the ruler be a Shape with an associated enum to represent the tick marks needed. Each iteration of the shape would draw exactly 1 section of the ruler. By doing this, you gain more control and more precision as to how to draw it, with 1 ForEach and simple repeatability. The shape could be defined as:
struct Unit: Shape {
let num: Int
let ticks: [Tick]
func path(in rect: CGRect) -> Path {
let distance = rect.width / CGFloat(ticks.count)
var path = Path()
var x = rect.minX
for tick in ticks {
switch tick {
case .major:
path.move(to: CGPoint(x: x, y: rect.minY))
path.addLine(to: CGPoint(x: x, y: rect.maxY))
case .mid:
path.move(to: CGPoint(x: x, y: rect.maxY))
path.addLine(to: CGPoint(x: x, y: rect.maxY - 16))
case .minor:
path.move(to: CGPoint(x: x, y: rect.maxY))
path.addLine(to: CGPoint(x: x, y: rect.maxY - 8))
}
x += distance
print("tick")
}
return path
}
}
enum Tick {
case major, mid, minor
}
Then your view would simplify to this:
struct Ruler: View {
let ticks: [Tick] = [.major, .minor, .mid, .minor, .mid, .minor, .mid, .minor]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 0) {
ForEach(1...20, id: \.self) { num in
Unit(num: num, ticks: ticks)
.stroke(.white, lineWidth: 2)
.frame(width: 100)
.overlay(
Text("\(num)")
.padding(.trailing, 2)
.foregroundColor(.white),
alignment: .topTrailing
)
}
}
.frame(height: 40)
.padding(.top, 2)
.padding(.bottom, 1)
.background(Color.black)
}
.padding()
}
}
I'm trying to add a circle background for an image, then clip the image using a semi-circle of the background. This is what I'm trying to end up with:
In SwiftUI, I put together something but stuck on what clipShape should I use:
Circle()
.fill(Color.blue)
.aspectRatio(contentMode: .fit)
.frame(width: 48)
.padding(.top, 8)
.overlay(
Image("product-sample1")
.resizable()
.scaledToFit()
//.clipShape(???)
)
This is what it looks like:
I'm not sure how to achieve this but was given a clue to use this approach:
How can this be achieved in SwiftUI, or is there a better approach to doing this?
You can create own shape. Below is a simple demo variant (for square content).
private struct DemoClipShape: Shape {
func path(in rect: CGRect) -> Path {
Path {
$0.move(to: CGPoint.zero)
$0.addLine(to: CGPoint(x: rect.maxX, y: 0))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
$0.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.midY, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false)
$0.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.midY, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false)
$0.addLine(to: CGPoint.zero)
}
}
}
And here is a sample of how it could be applied - due to padding we need to offset clipping (mask) to compensate locations (also it can be passed as constant into shape constructor, which will be more correct and accurate, but I leave it for you):
// used larger values for better visibility
Circle()
.fill(Color.blue)
.frame(width: 148, height: 148)
.padding(.top, 18)
.padding(.bottom, 10)
.overlay(
Image("product-sample1")
.resizable()
.scaledToFit()
)
.mask(DemoClipShape().offset(x: 0, y: -11)) // << here !!
gives
I'm trying to create an view like in the screenshot with swiftui. The top frame should be light, other places should be dark.
SwiftUI add inverted mask
I tried to change it over the codes here, but I couldn't. how can i do it?
where I've come so far :
black color not show full screen and mask not working
func HoleShapeMask(in rect: CGRect) -> Path {
return
Path { path in
path.move(to: CGPoint(x: rect.minX, y: rect.minY))
path.addLine(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, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
}
}
struct ContentView: View {
#State var rect :CGRect = .zero
var body: some View {
GeometryReader{g in
Text("deneme")
.foregroundColor(.blue)
.frame(width: 124, height: 100, alignment: .center)
.background(Color.red)
.overlay(
GeometryReader { gp in
Color.clear.opacity(0.00001)
.onAppear(){
rect = gp.frame(in: .named("deneme"))
}
}
)
}
.coordinateSpace(name: "deneme")
.overlay(
Rectangle()
.fill(Color.black.opacity(0.8))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center)
.mask(HoleShapeMask(in: rect).fill(style: FillStyle(eoFill: true)))
)
}
}
my code screenshot :
i want to this screen :
I'm trying to create a capsule style progress view in SwiftUI, basing my code on the popular circle progress style.
Here is the code:
struct PSOCapsuleProgressView: View{
var body: some View{
ZStack{
Text("20/99")
.font(.system(size: 12, weight: .light, design: .rounded))
Capsule(style: .circular)
.stroke(lineWidth: 3)
.foregroundColor(.red)
.padding(4)
.opacity(0.3)
Capsule(style: .circular)
.trim(from: 0.0, to: 0.5)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
}
}
}
This is how it looks:
My only issue is how to make the progress start from the middle top? The circle progress view's that I've seen use a rotation effect but in this case it won't work because its not circular.
Any help is greatly appreciated.
To solve this you need to construct your own Shape. The trim will start from where the shape starts so you have to start drawing your Shape from the top middle. This gives us something like this:
struct MyCapsule: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
let halfHeight = rect.height / 2
let halfWidth = rect.width / 2
path.move(to: CGPoint(x: halfWidth, y: 0))
path.addLine(to: CGPoint(x: rect.width - halfHeight, y: 0))
path.addCurve(to: CGPoint(x: rect.width, y: halfHeight),
control1: CGPoint(x: rect.width, y: 0),
control2: CGPoint(x: rect.width, y: halfHeight))
path.addCurve(to: CGPoint(x: rect.width - halfHeight, y: rect.height),
control1: CGPoint(x: rect.width, y: halfHeight),
control2: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: halfHeight, y: rect.height))
path.addCurve(to: CGPoint(x: 0, y: halfHeight),
control1: CGPoint(x: 0, y: rect.height),
control2: CGPoint(x: 0, y: halfHeight))
path.addCurve(to: CGPoint(x: halfHeight, y: 0),
control1: CGPoint(x: 0, y: 0),
control2: CGPoint(x: halfHeight, y: 0))
path.addLine(to: CGPoint(x: halfWidth, y: 0))
return path
}
}
Then we can set up our ContentView like this:
struct ContentView: View {
#State private var trim: CGFloat = 0
var body: some View {
ZStack {
MyCapsule()
.stroke(Color.red)
.opacity(0.6)
MyCapsule()
.trim(from: 0, to: trim)
.stroke(Color.red,
style: StrokeStyle(lineWidth: 4,
lineCap: .round,
lineJoin: .round))
Button(action: {
withAnimation(Animation.easeIn(duration: 2)) {
trim = trim == 0 ? 1 : 0
}
}, label: {
Text("Animate in")
})
}
.frame(width: 200, height: 100)
}
}
This gives the following result:
Note because it is a Shape it will be greedy and fill up as much of the View as it can, so make sure you set a frame around it.
It is a bit kludgy, but it appears the only way to do this is to add another Capsule and have the second Capsule run from 0.75 to 1.0, and the other to run from 0.0 to 0.25:
struct PSOCapsuleProgressView: View{
var body: some View{
ZStack{
Text("20/99")
.font(.system(size: 12, weight: .light, design: .rounded))
Capsule(style: .circular)
.stroke(lineWidth: 3)
.foregroundColor(.red)
.padding(4)
.opacity(0.3)
Capsule(style: .circular)
.trim(from: 0.75, to: 1.0)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
Capsule(style: .circular)
.trim(from: 0.0, to: 0.25)
.stroke(style: StrokeStyle(lineWidth: 3, lineCap: .round, lineJoin: .round))
.foregroundColor(.red)
.padding(4)
}
}
}
You will have to manage which capsule responds to the data. It may be possible, depending upon how your updates work to send the data into a ForEach as an array and create as many Capsules as you need in smaller increments.