Post

Replies

Boosts

Views

Activity

Reply to Issues with UserDefault boolean
As @MobileTen stated, never store passwords in UserDefaults. You need to use the Keychain. Also, I'm not sure why you need to store something called passcode-reset and passcode-set. The very fact that a password exists means it has been set, and if you're resetting a passcode, then that should be done as part of the journey for the user, not something that needs storing anywhere. In other words, if the user wants to reset their password, ask them for a new one, and save the new password to the keychain. There's no need to store in UDs that you're resetting the password. So, here's how to use the keychain: let userAccount: String = "AuthenticatedUser" let passwordKey: String = "SomeSortOfStringIdentifyingThisIsAPasswordForYourApp" // Choose a relevant value and never change it, as the password is stored against this value func getKeychainPasscode() -> String { return KeychainService.loadPassword(service: passwordKey, account: userAccount) ?? "" } // getKeychainPasscode() will tell you if a password is set. If it returns "" then it hasn't been set. func updatePasscode(_ value: String) { KeychainService.updatePassword(service: passwordKey, account: userAccount, data: value) } // Arguments for the keychain queries let kSecClassValue = NSString(format: kSecClass) let kSecAttrAccountValue = NSString(format: kSecAttrAccount) let kSecValueDataValue = NSString(format: kSecValueData) let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword) let kSecAttrServiceValue = NSString(format: kSecAttrService) let kSecMatchLimitValue = NSString(format: kSecMatchLimit) let kSecReturnDataValue = NSString(format: kSecReturnData) let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne) public class KeychainService: NSObject { class func updatePassword(service: String, account: String, data: String) { if let dataFromString: Data = data.data(using: String.Encoding.utf8, allowLossyConversion: false) { // Instantiate a new default keychain query let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue]) let status = SecItemUpdate(keychainQuery as CFDictionary, [kSecValueDataValue : dataFromString] as CFDictionary) if(status == errSecItemNotFound) { // No existing passcode, so just save the new one savePassword(service: service, account: account, data: data) } else { // Passcode exists, so delete it and save the new one removePassword(service: service, account: account) savePassword(service: service, account: account, data: data) } } } class func removePassword(service: String, account: String) { // Instantiate a new default keychain query let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue]) // Delete any existing items let status = SecItemDelete(keychainQuery as CFDictionary) if(status != errSecSuccess) { if let err = SecCopyErrorMessageString(status, nil) { print("Remove failed: \(err)") } } } class func savePassword(service: String, account: String, data: String) { if let dataFromString = data.data(using: String.Encoding.utf8, allowLossyConversion: false) { // Instantiate a new default keychain query let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue]) // Add the new keychain item let status = SecItemAdd(keychainQuery as CFDictionary, nil) // Always check the status if (status != errSecSuccess) { if let err = SecCopyErrorMessageString(status, nil) { print("Write failed: \(err)") } } } } class func loadPassword(service: String, account: String) -> String? { // Instantiate a new default keychain query // Tell the query to return a result // Limit our results to one item let keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, account, kCFBooleanTrue ?? true, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue]) var dataTypeRef: AnyObject? // Search for the keychain items let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef) var contentsOfKeychain: String? if(status == errSecSuccess) { if let retrievedData = dataTypeRef as? Data { contentsOfKeychain = String(data: retrievedData, encoding: String.Encoding.utf8) } } return contentsOfKeychain } }
2w
Reply to Double-tap to activate a UITextField?
If single-tapping on a text field in your app can be calamitous I'd say your app isn't designed properly. You need to revisit the user experience. Also, since text fields exist across the entire OS and users expect them to be activated when they single-tap them, changing this just for your app will ruin a user's 'muscle memory' and give them a poor experience.
2w
Reply to Mac os 15, breaking Citrix VPN
This page on Citrix's website doesn't mention macOS 15 as a supported version, so it seems you'll have to wait for Citrix to release a compatible version, and I'd suggest you contact them: https://docs.citrix.com/en-us/citrix-secure-private-access/service/spa-csa-for-client-server-apps.html
3w
Reply to Wind Compass swift UI
No problem. Just remember not to redraw the entire view when you change the degrees. You only need to change the rotationEffect of the compass marker arrow (and whatever text you put in the VStack) when the value changes.
3w
Reply to Wind Compass swift UI
Something like this? This is what I wrote and use in my own app, so if you use it, please make it look a little different... or I'll sue 😉 import SwiftUI struct ContentView: View { var body: some View { let windDegrees = 255.0 let frameSize = 240.0 ZStack { LinearGradient(gradient: Gradient(colors: [Color.init(red: 91.0/255.0, green: 100.0/255.0, blue: 157.0/255.0), Color.init(red: 125.0/255.0, green: 108.0/255.0, blue: 142.0/255.0)]), startPoint: .top, endPoint: .bottom) ZStack { Circle() .opacity(0.1) Image("compassMarker") .resizable() .scaledToFit() .frame(width: 10, height: 100) .rotationEffect(Angle(degrees: windDegrees - 180)) .shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1) Circle() .fill(.black.opacity(0.15)) .frame(width: frameSize/2, height: frameSize/2) VStack { Text(convertWindDirectionToCompassPoint(windDegrees)) .font(.system(size: 18, weight: .bold)) .foregroundStyle(.white) .shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1) Text("\(String(format: "%.0f", windDegrees))º") .font(.system(size: 12)) .foregroundStyle(.white) .lineLimit(1) .minimumScaleFactor(0.6) .shadow(color: .black.opacity(0.6), radius: 1, x: 1, y: 1) } ForEach(CompassMarker.markers(), id: \.self) { marker in CompassMarkerView(marker: marker, compassDegress: 0) } } .frame(width: frameSize, height: frameSize) } .ignoresSafeArea() } struct CompassMarker: Hashable { let degrees: Double let label: String init(degrees: Double, label: String = "") { self.degrees = degrees self.label = label } static func markers() -> [CompassMarker] { return [ CompassMarker(degrees: 0, label: "N"), CompassMarker(degrees: 30), CompassMarker(degrees: 60), CompassMarker(degrees: 90, label: "E"), CompassMarker(degrees: 120), CompassMarker(degrees: 150), CompassMarker(degrees: 180, label: "S"), CompassMarker(degrees: 210), CompassMarker(degrees: 240), CompassMarker(degrees: 270, label: "W"), CompassMarker(degrees: 300), CompassMarker(degrees: 330) ] } func degreeText() -> String { return String(format: "%.0f", self.degrees) } } struct CompassMarkerView: View { let marker: CompassMarker let compassDegress: Double var body: some View { VStack { Capsule() .frame(width: 2, height: self.capsuleHeight()) .foregroundStyle(Color.white) .opacity(0.6) .padding(.bottom, self.capsulePadding()) Text(marker.label) .font(.system(size: 16, weight: .bold)) .foregroundStyle(Color.white) .opacity(0.6) .rotationEffect(self.textAngle()) Spacer(minLength: 97) } .rotationEffect(Angle(degrees: marker.degrees)) } private func capsuleHeight() -> CGFloat { return (marker.label != "" ? 8 : 12) } private func capsulePadding() -> CGFloat { return (marker.label != "" ? -12 : -6) } private func textAngle() -> Angle { return Angle(degrees: -self.compassDegress - self.marker.degrees) } } func convertWindDirectionToCompassPoint(_ degrees: Double) -> String { var degrees_: Double = fmod(degrees, 360.0) var point: String = "" if(degrees_ > 360) { degrees_ = degrees_.truncatingRemainder(dividingBy: 360) } if((degrees_ >= 0.0 && degrees_ <= 11.25) || (degrees_ > 348.75 && degrees_ <= 360.0)) { point = "N" } if(degrees_ > 11.25 && degrees_ <= 33.75) { point = "NNE" } if(degrees_ > 33.75 && degrees_ <= 56.25) { point = "NE" } if(degrees_ > 56.25 && degrees_ <= 78.75) { point = "ENE" } if(degrees_ > 78.75 && degrees_ <= 101.25) { point = "E" } if(degrees_ > 101.25 && degrees_ <= 123.75) { point = "ESE" } if(degrees_ > 123.75 && degrees_ <= 146.25) { point = "SE" } if(degrees_ > 146.25 && degrees_ <= 168.75) { point = "SSE" } if(degrees_ > 168.75 && degrees_ <= 191.25) { point = "S" } if(degrees_ > 191.25 && degrees_ <= 213.75) { point = "SSW" } if(degrees_ > 213.75 && degrees_ <= 236.25) { point = "SW" } if(degrees_ > 236.25 && degrees_ <= 258.75) { point = "WSW" } if(degrees_ > 258.75 && degrees_ <= 281.25) { point = "W" } if(degrees_ > 281.25 && degrees_ <= 303.75) { point = "WNW" } if(degrees_ > 303.75 && degrees_ <= 326.25) { point = "NW" } if(degrees_ > 326.25 && degrees_ <= 348.75) { point = "NNW" } return point } } #Preview { ContentView() } Pass in your windDegrees (line 5) as a Double. Oh, and here's an image for the compassMarker arrow: The size of the compass is constrained by the frameSize so if you change it you'll need to alter the arrow image. You also don't need to show the degrees and 'WSW' text (for example), and can easily put your own text in the VStack.
3w