Button Stroke Set Gap In specific position Swiftui - button

I have added the borders for all sides using code below in SwiftUI. I want to trim stroke a particular position like below.
Want to achieve:-
Output:-
Code:-
import SwiftUI
struct RoundTrimImage: View {
var body: some View {
VStack{
Button(action: {
print("Round Action")
}) {
Text("Press")
.frame(width: 100, height: 100)
.foregroundColor(Color.black)
.background(Color.red)
.clipShape(Circle()).padding(5)
.overlay(
RoundedRectangle(cornerRadius: 100)
.trim(from: 0, to: CGFloat(0.8))
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
}
Question: Can someone please explain to me how to trim stoke particular position, I've tried with above code but no results yet.
Can someone please explain to me How to get Progress?
Any help would be greatly appreciated.
Thanks in advance.

The possible approach is to prepare needed shape using Arc and then rotate it to what ever position needed.
Demo prepared with Xcode 13.2 / iOS 15.2
struct DemoView: View {
var body: some View {
VStack{
Button(action: {
print("Round Action")
}) {
Text("Press")
.frame(width: 100, height: 100)
.foregroundColor(Color.black)
.background(Color.red)
.clipShape(Circle()).padding(5)
.overlay(
RotatedShape(shape: CutShape(), angle: .degrees(-120))
.stroke(Color.blue, lineWidth: 2)
)
}
}
}
}
private struct CutShape: Shape {
func path(in rect: CGRect) -> Path {
Path {
$0.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.midY, startAngle: Angle(degrees: -5), endAngle: Angle(degrees: 5), clockwise: true)
}
}
}

Related

SwiftUI - How to perform action only while the button is tapped, and end it when the tap is released?

I'm trying to recreate a Game Boy like game pad in SwiftUI. Graphically it looks good, but I can't make the actions work. I would like it to perform the action (move in the selected direction) while the arrow is tapped, and to stop moving once the arrow isn't tapped anymore (just like a real game pad would). The code I tried so far is this one:
import SwiftUI
struct GamePad: View {
#State var direction = "Empty"
#State var animate = false
var body: some View {
ZStack {
VStack {
Text("\(direction) + \(String(describing: animate))")
.padding()
Spacer()
}
VStack(spacing: 0) {
Rectangle()
.frame(width: 35, height: 60)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Up"
animate = true
} label: {
VStack {
Image(systemName: "arrowtriangle.up.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
Rectangle()
.frame(width: 35, height: 60)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Down"
animate = true
} label: {
VStack {
Spacer()
Image(systemName: "arrowtriangle.down.fill")
.foregroundColor(.black.opacity(0.4))
}
.padding(.bottom, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
}
HStack(spacing: 35) {
Rectangle()
.frame(width: 43, height: 35)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Left"
animate = true
} label: {
VStack {
Image(systemName: "arrowtriangle.left.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
Rectangle()
.frame(width: 43, height: 35)
.foregroundColor(.gray.opacity(0.3))
.overlay(
Button {
direction = "Right"
animate = true
} label: {
VStack {
Spacer()
Image(systemName: "arrowtriangle.right.fill")
.foregroundColor(.black.opacity(0.4))
}
.padding(.bottom, 10)
.gesture(
TapGesture()
.onEnded({ () in
direction = "Ended"
animate = false
})
)
}
)
}
}
}
}
What am I doing wrong? Thanks
It can be achieved with custom button style, because it has isPressed state in configuration.
Here is a demo of possible solution. Tested with Xcode 13.4 / iOS 15.5
struct StateButtonStyle: ButtonStyle {
var onStateChanged: (Bool) -> Void
func makeBody(configuration: Configuration) -> some View {
configuration.label
.opacity(configuration.isPressed ? 0.5 : 1) // << press effect
.onChange(of: configuration.isPressed) {
onStateChanged($0) // << report if pressed externally
}
}
}
and updated button with it
Button {
direction = "Ended" // action on touchUP
} label: {
VStack {
Image(systemName: "arrowtriangle.up.fill")
.foregroundColor(.black.opacity(0.4))
Spacer()
}
.padding(.top, 10)
}
.buttonStyle(StateButtonStyle { // << press state is here !!
animate = $0
if $0 {
direction = "Up"
}
})
Test module on GitHub

Want to show zoom in zoom out on map through buttons in swiftUI

I want to add 2 buttons on a map in swiftUI Map to zoom in and out of it. I have positioned the two buttons at the bottom of the screen in a Stack inside a VStack. I need the code to make them work.
This is what I have:
import Foundation
import SwiftUI
import MapKit
struct QuakeDetail: View {
var quake: Quake
#State private var region : MKCoordinateRegion
init(quake : Quake) {
self.quake = quake
_region = State(wrappedValue: MKCoordinateRegion(center: quake.coordinate,
span: MKCoordinateSpan(latitudeDelta: 3, longitudeDelta: 3)))
}
var body: some View {
ZStack {
VStack {
Map(coordinateRegion: $region, annotationItems: [quake]) { item in
MapMarker(coordinate: item.coordinate, tint: .red)
} .ignoresSafeArea()
HStack {
Button {
print ("pressed +")
} label: {
HStack {
Text("map")
Image(systemName: "plus")
}
}.padding(5).border(Color.blue, width: 1)
Spacer()
QuakeMagnitude(quake: quake)
Spacer()
Button {
print ("pressed -")
} label: {
HStack {
Text("map")
Image(systemName: "minus")
}
}.padding(5).border(Color.blue, width: 1)
}.padding()
Text(quake.place)
.font(.headline)
.bold()
Text("\(quake.time.formatted())")
.foregroundStyle(Color.secondary)
Text("\(quake.latitude) \(quake.longitude)")
}
}
}
}
You have to operate with span parameter of MKCoordinateRegion for that (the less value the more zoomed area), like (demo zoom on 10%):
Button("Zoom In") {
region.span.latitudeDelta *= 0.9
region.span.longitudeDelta *= 0.9
}
Tested with Xcode 13.2 / iOS 15.2

Tappable Image in a ZStack with a Button

I have a ZStack with an image and a close button overlaying it. I cannot make the image tappable with the button overlaying it.
ZStack(alignment: .topTrailing) {
NetworkImage(url: article.imageURL)
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height * 0.5)
.scaleEffect(scrollViewContentOffset < 0 ?
1 + (-scrollViewContentOffset * 0.005)
: 1,
anchor: .bottom)
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}){
Image(systemName: "xmark.circle.fill")
.foregroundColor(.init(white: 0.9)).padding([.top, .trailing])
.font(.system(size: topCloseButtonSize))
.offset(y: scrollViewContentOffset < 0 ?
.extraLarge + scrollViewContentOffset :
.extraLarge)
}
}
I have tried to add a tap gesture to the Image itself but that disables the tapability on the close button. How can I keep the ZStack in tact and have both the image and close button tappable?
Try adding an onTapGesture modifier to the NetworkImage (Rectangle in the code beloew) - it seems to work as expected then. I simplified the NetworkImage to use a standard Rectangle, but the tap results should be the same. Tested on Xcode 12.1.
ZStack(alignment: .topTrailing) {
Rectangle() // Use NetworkImage here - simplified to Rectangle for testing
.aspectRatio(contentMode: .fill)
.frame(width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height * 0.5)
.onTapGesture(count: 1, perform: {
print("RECTANGLE TAPPED!")
})
Button(action: {
print("CLOSE TAPPED")
}){
Image(systemName: "xmark.circle.fill")
.foregroundColor(.init(white: 0.9)).padding([.top, .trailing])
}
}

Non-Nil User Crashing When Unwrapped - Firebase, SwiftUI

Unwrapping values the conventional way in SwiftUI views is not allowed, but you can do so within the body of, say, an HStack or a VStack. Below I am trying to unwrap a user I was certain was not nil. My LoginView lets a user create an account on Firebase Firestore, logs you in if a user enters the correct values, and shows an alert view if not.
In my HomeView.swift, the first screen after launch, I crash when trying to present the user image. However, when I unwrap the user, the user is still nil in the console. I think this is because Firebase doesn't have time to load the image before the view initializes. But I could be wrong. Help.
struct HomeProfileView: View {
#EnvironmentObject var session: SessionStore
#State var showDashboard = false
var user: User?
var body: some View {
if user != nil {
URLImage(URL(string: user!.profileImageUrl)!, content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
.clipShape(Circle())
})
.frame(width: 50, height: 50)
.background(Color(#colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1)))
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)
.sheet(isPresented: $showDashboard) {
DashboardView(showDashboard: $showDashboard)
}
}
}
}
I don't think I need an optional user, here, but I do not know how to safely unwrap the user otherwise. Another version is using the session EnvironmentObject to access the currently logged user.
struct HomeProfileView: View {
#EnvironmentObject var session: SessionStore
#State var showDashboard = false
var body: some View {
if session.isLoggedIn {
URLImage(URL(string: session.userSession!.profileImageUrl)!, content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
.clipShape(Circle())
})
.frame(width: 50, height: 50)
.background(Color(#colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1)))
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)
.sheet(isPresented: $showDashboard) {
DashboardView(showDashboard: $showDashboard)
}
}
}
}
The session property belongs to a SessionStore class and is essentially an optional User.
import Foundation
import Combine
import Firebase
class SessionStore: ObservableObject {
#Published var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: "isLoggedIn") {
didSet {
UserDefaults.standard.set(self.isLoggedIn, forKey: "isLoggedIn")
}
}
var userSession: User?
var handle: AuthStateDidChangeListenerHandle?
func listenAuthenticationState() {
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if let user = user {
print(user.email as Any)
let firestoreUserId = Ref.FIRESTORE_DOCUMENT_USERID(userId: user.uid)
firestoreUserId.getDocument { (document, error) in
if let dict = document?.data() {
guard let decodeUser = try? User.init(fromDictionary: dict) else { return }
self.userSession = decodeUser
}
}
self.isLoggedIn = true
} else {
print("User is Logged Out")
self.isLoggedIn = false
self.userSession = nil
}
})
}
}
The logged in user is indeed logged in but I cannot access any properties.
Set isLoggedIn exactly at the place you set userSession, because it is done in async callback, and do this on main queue (to update UI properly)
firestoreUserId.getDocument { (document, error) in
if let dict = document?.data() {
guard let decodeUser = try? User.init(fromDictionary: dict) else { return }
DispatchQueue.main.async {
self.userSession = decodeUser
self.isLoggedIn = true
}
}
}
I have this working. Not sure how scalable it is but I am doing a not equals nil. I also put it in a button to toggle it versus an onTapGesture.
struct HomeProfileView: View {
#EnvironmentObject var session: SessionStore
#State var showDashboard = false
var body: some View {
if session.isLoggedIn {
if (session.userSession?.profileImageUrl) != nil {
Button(action: { self.showDashboard.toggle() } ) {
URLImage(URL(string: session.userSession!.profileImageUrl)!, content: {
$0.image
.resizable()
.aspectRatio(contentMode: .fill)
.clipShape(Circle())
})
.frame(width: 50, height: 50)
.background(Color(#colorLiteral(red: 0.9490196078, green: 0.9490196078, blue: 0.9490196078, alpha: 1)))
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.1), radius: 1, x: 0, y: 1)
.shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 10)
.sheet(isPresented: $showDashboard) {
DashboardView(showDashboard: $showDashboard)
}
}
}
}
}
}

SwiftUI: How to Navigate from one view to another by clicking on a button

I am trying to move from one view to another, I have used NavigationLink as following:
#State private var isActive = false
NavigationLink(destination: DetailsView(),
isActive: $isActive) {
Text("")}
Button(action: {
print ("Clicked")
self.isActive = true
}){
Text("More Details")
.font(.headline)
.bold()
.padding(.all, 10)
.padding(.leading, 10)
.padding(.trailing, 10)
.foregroundColor(.white)
.background(Color.orange.frame(width: 300, height: 50) .clipShape(RoundedRectangle(cornerRadius: 20)))
}
Unfortunately did not work!! The button did not take me to Details View!! I have already checked most of the solutions over here but nothing worked for me :( Please help!
To use a NavigationLink you have to wrap it in a NavigationView
struct ContentView: View {
var body: some View {
NavigationView { // <--- add this
NavigationLink(destination: DetailsView()) {
Text("ADD TO CART")
.font(.headline)
.bold()
.padding(.all, 10)
.padding(.leading, 10)
.padding(.trailing, 10)
.foregroundColor(.white)
.background(Color.orange.frame(width: 300, height: 50) .clipShape(RoundedRectangle(cornerRadius: 20)))
}
}
}
}

Resources