Path transparent only on macOS - swift

Why are paths slightly transparent on macOS and not iOS? How can I make them not transparent on macOS?
Minimal reproducible example:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Path { path in
path.move(to: CGPoint(x: 150, y: 150))
path.addLine(to: CGPoint(x: 150, y: 300))
}
.stroke(lineWidth: 8)
Path { path in
path.move(to: CGPoint(x: 150, y: 200))
path.addLine(to: CGPoint(x: 150, y: 450))
}
.stroke(lineWidth: 8)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.preferredColorScheme(.dark)
}
}

Related

Custom Menu/Tab bar in SwiftUI

I want to make tab bar like this and do not know where to start. Please help me I'm a newbie!
It navigates to different views.
Here's a pretty functional version. This could be made better to further mirror SwiftUI's TabBar interface. Let me know if you run into any issues with this.
(full res)
protocol TopTab: RawRepresentable, Identifiable, CaseIterable, Hashable where AllCases == Array<Self> {
var title: String { get }
}
struct TabBackground: Shape {
var cornerRadius: CGFloat = 8
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: .zero + cornerRadius, y: .zero))
path.addLine(to: CGPoint(x: rect.width - cornerRadius, y: .zero))
path.addQuadCurve(to: CGPoint(x: rect.width, y: .zero + cornerRadius), control: CGPoint(x: rect.width, y: .zero))
path.addLine(to: CGPoint(x: rect.width, y: rect.height - cornerRadius))
path.addQuadCurve(to: CGPoint(x: rect.width + cornerRadius, y: rect.height), control: CGPoint(x: rect.width, y: rect.height))
path.addLine(to: CGPoint(x: .zero - cornerRadius, y: rect.height))
path.addQuadCurve(to: CGPoint(x: .zero, y: rect.height - cornerRadius), control: CGPoint(x: .zero, y: rect.height))
path.addLine(to: CGPoint(x: .zero, y: cornerRadius))
path.addQuadCurve(to: CGPoint(x: .zero + cornerRadius, y: .zero), control: .zero)
return path
}
}
struct TopScrollingTabBarPicker<Tab: TopTab>: View {
#Binding var selection: Tab
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(Tab.allCases) { tab in
Group {
if tab == selection {
Text(tab.title)
.bold()
.foregroundColor(.accentColor)
.padding(12)
.background {
TabBackground(cornerRadius: 16)
.fill(Color.white)
}
} else {
Text(tab.title)
.padding(4)
.foregroundColor(.white)
}
}
.tag(tab)
.onTapGesture {
selection = tab
}
}
}
.padding(.horizontal, 10)
}
.background(Color.accentColor)
}
}
struct TopScrollingTabBar<Tab: TopTab, Content: View>: View {
var alignment: HorizontalAlignment = .leading
#Binding var selection: Tab
#ViewBuilder var content: (_ tab: Tab) -> Content
var body: some View {
GeometryReader { geometry in
VStack(alignment: alignment) {
TopScrollingTabBarPicker(selection: $selection)
.frame(height: 44)
ZStack {
Color(.systemBackground)
.ignoresSafeArea()
self.content(selection)
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
}
}
}
}
}
Usage:
enum PreviewTab: String, TopTab {
case saved
case booked
case pending
case confirmed
case archived
var id: String {
rawValue
}
var title: String {
switch self {
case .saved:
return "Saved"
case .booked:
return "Booked"
case .pending:
return "Pending"
case .confirmed:
return "Confirmed"
case .archived:
return "Archived"
}
}
}
struct ContentView: View {
#State var selection: PreviewTab = .saved
var body: some View {
TopScrollingTabBar(selection: $selection.animation()) { tab in
// For demo purposes
List {
Text(tab.title)
.bold()
.font(.largeTitle)
Text("Something that is \(tab.title)")
}
.listStyle(.plain)
// In a full app I'd switch on the Tab enum and generate the appropriate view.
// switch tab {
// case .saved:
// SavedThings()
// case .booked:
// ...etc
// }
}
}
}
Hope this helps! <3

SwiftUI animate opacity in gradient mask

I want to animate a mountain line chart (see screenshot) so that the line appears smoothly from left to right. I tried animating the endPoint of a LinearGradient in a mask. This works as it should, but leaves the right end of the line with an opacity value < 1. I also tried animating the opacity value itself, but that doesn't animate anything. This is the View:
import SwiftUI
struct ContentView: View {
private var datapoints: [CGFloat] = [100, 250, 200, 220, 200, 250, 350, 300, 325, 250]
#State private var pct: Double = 0.0
var body: some View {
GeometryReader { geometry in
ZStack {
Path() { path in
var x = 0.0
path.move(to: CGPoint(x: x, y: datapoints[0]))
for i in (1 ..< datapoints.count) {
x += geometry.size.width / CGFloat(datapoints.count - 1)
path.addLine(to: CGPoint(x: x, y: datapoints[i]))
}
}
.stroke()
Path() { path in
var x: Double = 0.0
path.move(to: CGPoint(x: x, y: datapoints[0]))
for i in (1 ..< datapoints.count) {
x += geometry.size.width / CGFloat(datapoints.count - 1)
path.addLine(to: CGPoint(x: x, y: datapoints[i]))
}
path.addLine(to: CGPoint(x: path.currentPoint!.x, y: geometry.size.height))
path.addLine(to: CGPoint(x: 0, y: geometry.size.height))
}
.fill(LinearGradient(colors: [.black.opacity(0.25), .black.opacity(0)], startPoint: .top, endPoint: .bottom))
}
.mask(LinearGradient(stops: [Gradient.Stop(color: .black, location: 0), Gradient.Stop(color: .black.opacity(pct), location: 1)], startPoint: .leading, endPoint: .trailing))
.onAppear() {
withAnimation(.linear(duration: 3)) {
pct = 1.0
}
}
// .mask(LinearGradient(stops: [Gradient.Stop(color: .black, location: 0), Gradient.Stop(color: .black.opacity(0), location: 1)], startPoint: .leading, endPoint: pct == 1 ? .trailing : .leading))
// .onAppear() {
// withAnimation(.linear(duration: 3)) {
// pct = 1.0
// }
// }
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This is the result if uncomment the commented out lines:

SwiftUI strangely shifting location of certain Views when invoking contextMenu modifier

I'm having an issue where some SwiftUI views (under certain configurations) are shifting their location to the center of the screen when I use the .contextMenu(menuItems:) modifier, and I can't figure out why. I've recreated the problem in a simplified manner in the following code samples:
struct ContentView: View {
var body: some View {
Path { path in
path.move(to: CGPoint(x: 200, y: 100))
path.addLine(to: CGPoint(x: 100, y: 300))
path.addLine(to: CGPoint(x: 300, y: 300))
path.addLine(to: CGPoint(x: 200, y: 100))
}
.contextMenu {
Text("hello world")
}
}
}
GIF that demonstrates how the triangle shifts to center screen for some reason due to the context menu invocation
struct ContentView: View {
var body: some View {
Circle()
.position(x: 0, y: 0)
.frame(width: 50, height: 50, alignment: .bottom)
.contextMenu {
Text("hello world")
}
}
}
GIF that demonstrates how the circle shifts to center screen and somehow gains a sharp edge due to the context menu invocation
I would really appreciate it if anyone knows what's going on here. I'm on Xcode 12.5.1, macOS 11.4, and iOS 14.5.
By adding certain frame to shape you can fix the issue but I don't have any explanation.
Path { path in
path.move(to: CGPoint(x: 200, y: 100))
path.addLine(to: CGPoint(x: 100, y: 300))
path.addLine(to: CGPoint(x: 300, y: 300))
path.addLine(to: CGPoint(x: 200, y: 100))
}
.frame(width: 400, height: 400, alignment: .center)
.contextMenu {
Text("hello world")
}

How can I clip a Shape with another Shape in SwiftUI?

I want to clip the red square with the green half-circle. I tried .mask and .clipShape but
i can't get this to work. Can someone share how its done please?
import SwiftUI
struct TestEndBarView: View {
var body: some View {
ZStack {
Rectangle()
.fill(Color.red)
.frame(width: 500, height: 500)
Circle()
.trim(from: 0.5, to: 1.0)
.fill(Color.green)
.rotationEffect(Angle(degrees: 90))
.frame(width: 500, height: 500)
.offset(x: -250)
}
}
}
struct TestEndBarView_Previews: PreviewProvider {
static var previews: some View {
TestEndBarView()
}
}
with using eoFill like in the code:
struct ContentView: View {
var body: some View {
CustomShape()
.fill(Color.purple, style: FillStyle(eoFill: true, antialiased: true))
.frame(width: 200, height: 200, alignment: .center)
.shadow(radius: 10.0)
}
}
struct CustomShape: Shape {
func path(in rect: CGRect) -> Path {
return Path { path in
path.addArc(center: CGPoint(x: rect.minX, y: rect.midY), radius: rect.height/2, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 270), clockwise: true)
path.addRect(CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: rect.height))
}
}
}
Create a struct that conforms to Shape.
Implement func path(in rect: CGRect) -> Path
Create a Path. Add the rectangle, then the circle.
Fill the shape with isEOFilled set to true
My code is this basic one. Maybe someone finds this useful so i am attaching it here as well.
import SwiftUI
struct TestEndBarView: View {
var body: some View {
ZStack {
BoxCutOut()
.foregroundColor(Color.red)
}
}
}
struct TestEndBarView_Previews: PreviewProvider {
static var previews: some View {
TestEndBarView()
}
}
struct BoxCutOut: Shape {
func path(in rect: CGRect) -> Path {
Path { path in
path.move(to: CGPoint(x:0, y: 0))
path.addLine(to: CGPoint(x:rect.width, y:0))
path.addLine(to: CGPoint(x:rect.width, y:rect.height))
path.addLine(to: CGPoint(x:0, y:rect.height))
path.addArc(center: CGPoint(x: 0, y: rect.height/2), radius: rect.height/2, startAngle: .degrees(90), endAngle: .degrees(270), clockwise: true )
}
}
}

SwiftUI: Change the start position for animated progress bar (or Shape drawing in general)

The code below begins to draw a rectangle from the bottom left corner. How can I change it to begin to draw from the top middle? Hopefully there is a simple setting I am missing here.
#State private var progress: Double = 0
#State private var timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
VStack{
HStack{
ZStack {
Rectangle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.gray)
.rotationEffect(Angle(degrees: 270.0))
.frame(width: 100, height:100)
Rectangle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.blue)
.rotationEffect(Angle(degrees: 270.0))
.animation(.linear)
.frame(width: 100, height:100)
.onReceive(timer, perform: { _ in
if(progress < 100){
progress += 0.1/10
}
else{
progress = 0
}
})
}
}
.padding()
}
}
You don't need to rotate it (moreover it might be not square), just create own rectangular shape with start point anywhere needed.
Demo prepared with Xcode 12.4 / iOS 14.4
struct MyRectangle: Shape {
func path(in rect: CGRect) -> Path {
Path {
$0.move(to: CGPoint(x: rect.midX, y: rect.minY))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
$0.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
$0.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
$0.addLine(to: CGPoint(x: rect.minX, y: rect.minY))
$0.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
}
}
}
struct ContentView: View {
#State private var progress: Double = 0
#State private var timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect()
var body: some View {
VStack{
HStack{
ZStack {
MyRectangle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.gray)
.frame(width: 200, height:100)
MyRectangle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 1.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.blue)
.animation(.linear)
.frame(width: 200, height:100)
.onReceive(timer, perform: { _ in
if(progress < 100){
progress += 0.1/10
}
else{
progress = 0
}
})
}
}
.padding()
}
}
}