Hi
I reviewed this post:
How to change navigation title color in swiftUI
This seems to work fine if the intention is to apply the title color across an application. I would like to apply a different text title color selectively depending on the View being shown. And in some instances revert back to the color depending on the light and dark themes.
Same result occurs using a viewmodifier or simply using onAppear and onDisappear with the title color is applied to all views. And if you do modify it in onDisappear, when you navigate back to another view which changes the color onAppear it has the same color as the previous view.
The only way I've found this to work is using UIViewControllerRepresentable and handling the viewWillAppear and viewWillDisappear something like this:
NavigationBarView(
viewWillAppear: { nav in
nav.navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
},
viewWillDisappear: { nav in
nav.navigationBar.largeTitleTextAttributes = nil
}
)
Has anyone been successful in getting a different title text color to apply to different views using a modifier or onAppear and onDisappear?
Appreciate any guidance.
Post
Replies
Boosts
Views
Activity
Hi
I have a TabView and I would like a background image to cover the full screen. Here is the code I'm struggling with:
struct ContentView: View {
var body: some View {
ZStack {
Image("background-image")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
TabView {
AssetsView()
.tabItem {
Label("Assets", systemImage: "books.vertical.fill")
}
FavouriteView()
.tabItem {
Label("Favourite", systemImage: "star.fill")
}
}
}
}
No matter what I try, I can't seem to get my "background-image" to display. I thought I might just have the background image only on AssetsView but that wasn't successful either.
Any ideas would be appreciated.
HiI recently updated to Xcode 11 GM seed. However I've noticed that the dreaded "Unknown class in Interface Builder file" is crashing my app. I haven't changed any class names or storyboards. Interestingly the app runs perfectly in teh simulator, but crashed on my phone.Here is what is being printed in the output window:MyAppName[9513:4230222] Unknown class _TtC10MyApp24SlideTableViewController in Interface Builder file.Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).MyAppName[9513:4230222] Could not cast value of type 'UIViewController' (0x1ebe282b0) to 'MyApp.SlideTableViewController' (0x104d05e08).I've deleted the class and recreated, removed the View Controller from the story board, made sure the view controller is references correctly as is the target, but the problem persists and I'm out of ideas.Is there a "reset" of the storyboard to reference the elements? Or some other way to resolve this?Many thanksCraig
Hi
I'm trying to add to the Keychain with access control flags. However the OSStatus returns -50 One or more parameters passed to a function were not valid.
Here is the function I've written causing the error:
public func addItem(value: Data, forKey: String, accessControlFlags: SecAccessControlCreateFlags? = nil) {
guard !forKey.isEmpty else {
return
}
var query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: Bundle.main.bundleIdentifier!,
kSecAttrAccount as String: forKey,
kSecValueData as String: value,
kSecAttrSynchronizable as String: false
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock]
// Check if any access control is to be applied.
if let accessControlFlags = accessControlFlags {
var error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, accessControlFlags, &error) else {
return
}
query[kSecAttrAccessControl as String] = accessControl
}
let status = SecItemAdd(query as CFDictionary, nil)
guard status != errSecDuplicateItem else {
return
}
guard status == errSecSuccess else {
let message = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown error"
print(message)
return
}
}
Any ideas why this might be occurring, if kSecAttrAccessControl is not added to the query parameter, then it works fine.
Thanks
HiI'm using the new CryptoKit to generate a 6 or 8 digit TOTP code. Anyone been successful doing this?Using Xcode 11 BETA 5, targeting iOS 13 and Swift 5.1. Here is a snippet of generating an TOTP via CommonCrypto versus CryptoKit in playground (BETA). The base32Decode function returns Data.import CryptoKit
import CommonCrypto
import Foundation
let period = TimeInterval(30)
let digits = 6
let secret = base32Decode(value: "5FAA5JZ7WHO5WDNN")!
var counter = UInt64(Date().timeIntervalSince1970 / period).bigEndian
func cryptoKitOTP() {
// Generate the key based on the counter.
let key = SymmetricKey(data: Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter)))
let hash = HMAC<Insecure.SHA1>.authenticationCode(for: secret, using: key)
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.byteCount - 1] & 0x0f
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CryptoKit OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func commonCryptoOTP() {
let key = Data(bytes: &counter, count: MemoryLayout.size(ofValue: counter))
let (hashAlgorithm, hashLength) = (CCHmacAlgorithm(kCCHmacAlgSHA1), Int(CC_SHA1_DIGEST_LENGTH))
let hashPtr = UnsafeMutablePointer.allocate(capacity: Int(hashLength))
defer {
hashPtr.deallocate()
}
secret.withUnsafeBytes { secretBytes in
// Generate the key from the counter value.
counterData.withUnsafeBytes { counterBytes in
CCHmac(hashAlgorithm, secretBytes.baseAddress, secret.count, counterBytes.baseAddress, key.count, hashPtr)
}
}
let hash = Data(bytes: hashPtr, count: Int(hashLength))
var truncatedHash = hash.withUnsafeBytes { ptr -> UInt32 in
let offset = ptr[hash.count - 1] & 0x0F
let truncatedHashPtr = ptr.baseAddress! + Int(offset)
return truncatedHashPtr.bindMemory(to: UInt32.self, capacity: 1).pointee
}
truncatedHash = UInt32(bigEndian: truncatedHash)
truncatedHash = truncatedHash & 0x7FFF_FFFF
truncatedHash = truncatedHash % UInt32(pow(10, Float(digits)))
print("CommonCrypto OTP value: \(String(format: "%0*u", digits, truncatedHash))")
}
func otp() {
commonCryptoOTP()
cryptoKitOTP()
}
otp()The output based on now as in 2:28pm is: CommonCrypto OTP value: 819944 CryptoKit OTP value: 745890To confirm the OTP value, I used oathtool which you can brew install to generate an array of TOTP's. For example:oathtool --totp --base32 5FAA5JZ7WHO5WDNN -w 10Craig
Hi
I have an array of protocols and would like to retrieve items from the array based on an enum. Something like this:
enum ServiceType: String {
case car
case boat
}
protocol ServiceDescriptor {
var contact: String { get }
var type: ServiceType { get }
}
struct CarService: ServiceDescriptor {
let contact: String
let type: ServiceType
}
struct BoatService: ServiceDescriptor {
let contact: String
let type: ServiceType
}
class Repair {
init(allowedRepairs: [ServiceDescriptor]) {
self.allowedRepairs = allowedRepairs
}
let allowedRepairs: [ServiceDescriptor]
subscript(type: ServiceType) -> ServiceDescriptor? {
get {
return allowedRepairs.first(where: { $0.type == type })
}
}
}
let carService = CarService(contact: "Fred", type: .car)
let boatService = BoatService(contact: "Jane", type: .boat)
let repair = Repair(allowedRepairs: [carService, boatService])
print(repair.allowedRepairs[type: ServiceType.boat]). // error
I end up with errors in Playgrounds as:
no exact matches in call to subscript
found candidate with type '(ServiceType) -> Array.SubSequence' (aka '(ServiceType) -> ArraySlice< ServiceDescriptor >')
The following works:
print(repair.allowedRepairs[0])
I'm obviously coming about this the wrong way, I wanted to avoid a dictionary i.e let allowedRepairs: [ServiceType: ServiceDescriptor] because that would lend itself to assigning the wrong ServiceDescriptor to a ServiceType and it kinda duplicates the data.
Is there a another way to have a custom argument in subscript instead of int?
Many thanks
Hi
I've built an SSO extension for my app. Now I would like to update the authsrv:login.myhost.com with additional associated domains generated by the MDM.
The video here at 9:10 mark references the MDM associated domain ApplicationAttributes for iOS as the way to go.
https://developer.apple.com/videos/play/tech-talks/301/
Is it just a matter of including:
com.apple.developer.associated-domains.mdm-managed=YES in the entitlement file for both the app and the extension and having the MDM push down something like this in the profile?
<dict>
<key>AssociatedDomains</key>
<array>
<string>authsrv:login.myhost2.com</string>
</array>
</dict>
Appreciate any guidance.
Hi
Have a question around extensions and app group capabilities.
I have an existing app using UserDefaults and if I want to introduce an SSO extension; the extension doesn't use or need any of the data created by the app and saved to UserDefaults.
Will the app still have access to the UserDefaults or do I have move to UserDefaults(suiteName: "group.com.YourCompany.YourApp") even if UserDefaults is only used by the app?
Many thanks
Hi
I've create a couple of files that are in the build target and "checked" the box to include these files in the test target.
Here are the files in the build and test targets.
DefaultValue+Extension.swift
extension Bool {
public enum False: DefaultValue {
public static let defaultValue = false
}
public enum True: DefaultValue {
public static let defaultValue = true
}
}
extension String {
public enum Empty: DefaultValue {
public static let defaultValue = ""
}
}
DefaultValuePropertyWrapper.swift
public protocol DefaultValue {
associatedtype Value: Decodable
static var defaultValue: Value {
get
}
}
@propertyWrapper
public struct Default<T: DefaultValue> {
public var wrappedValue: T.Value
public init() {
self.wrappedValue = T.defaultValue
}
}
extension Default: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try container.decode(T.Value.self)
}
}
extension Default: Encodable where T.Value: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.wrappedValue)
}
}
extension Default: Equatable where T.Value: Equatable {}
extension Default: Hashable where T.Value: Hashable{}
extension KeyedDecodingContainer {
public func decode<T>(_ type: Default<T>.Type, forKey key: Key) throws -> Default<T> where T: DefaultValue {
try decodeIfPresent(type, forKey: key) ?? .init()
}
}
Running the unit test I encounter a several errors which I haven't been able to resolve. Here is my swift file with only the test target checked.
DefaultValueTest.swift
import XCTest
@testable import MyFramework
class DefaultValueTests: XCTestCase {
override func setUpWithError() throws {
}
override func tearDownWithError() throws {
}
struct Person: Decodable {
let userId: Int
let name: String
@Default<String.Empty> var nickName: String
@Default<Bool.False> var isEnabled: Bool
@Default<Bool.True> var isAdmin: Bool
}
func testDefaultValues() throws {
let json = """
{
"userId": 1,
"name": "John"
}
"""
do {
let result = try JSONDecoder().decode(Person.self, from: json.data(using: .utf8)!)
XCTAssertTrue(result.nickname.isEmpty)
XCTAssertFalse(result.isEnabled)
XCTAssertTrue(result.isAdmin)
}
catch let error {
print("Error: \(error.localizedDescription)")
XCTFail()
}
}
The Person structure in DefaultValueTest.swift complains of the following:
Type DefaultValueTests.Person does not conform to protocol Decodable
nickName Ambiguous type name 'Empty' in 'String'
isEnabled Ambiguous type name 'False' in 'Bool'
isAdmin Ambiguous type name 'True' in 'Bool'
When I build the framework and use it another project, everything works as expected...
Appreciate any advice 🙏
Hi
First SwiftUI app and spent a few hours trying to fix the alignment with the following layout.
struct ContentView: View {
var body: some View {
VStack {
Text("About My Data")
.font(.title)
.fontWeight(.heavy)
.multilineTextAlignment(.center)
.padding()
Spacer().frame(height: 64)
HStack {
Image(systemName: "network")
.font(.system(size: 36.0))
.foregroundColor(.blue)
VStack(alignment: .leading) {
Text("Network Support")
.font(.headline)
.fontWeight(.bold)
Text("Download your data from anywhere.")
.font(.body)
.foregroundColor(.gray)
.multilineTextAlignment(.leading)
}
}.padding()
Spacer().frame(height:24)
HStack {
Image(systemName: "hand.raised.fill")
.font(.system(size: 36.0))
.foregroundColor(.blue)
VStack(alignment: .leading) {
Text("Data Privacy")
.font(.headline)
.fontWeight(.bold)
Text("Scanned certificates are never stored on the device.")
.font(.body)
.foregroundColor(.gray)
.multilineTextAlignment(.leading)
}
}.padding()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can see that the HStack alignments are off, the first (showing the network image) seems to have a extra padding , not sure how to correct it if anyone has some suggestions.
Many thanks
Hi
On first launch (after download) of my app I display a "terms and conditions" consent screen. When the user taps agree, I set a Bool value in UserDefaults, so on next launch, the user is not prompted again. Pretty stock standard approach.
I've had some users report that if the app is in the background for an extended period of time, the "terms and conditions" screen will re-appear when brought back into the foreground.
But if the app was terminated after use and then re-launched, then behaviour is as expected - the "terms and conditions" screen is not shown.
Is the better approach to use a file instead?
Thanks
HiI have created a private key in the Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly. When I attempt to access the key to perform a signing operation, the Touch ID dialog will sometimes appear, but in most cases I have to touch the biometry sensor, then the dialog is displayed. Here's the code I'm using to create the key and access the key in a sign operation.public static func create(with name: String, authenticationRequired: SecAccessControlCreateFlags? = nil) -> Bool
{
guard !name.isEmpty else
{
return false
}
var error: Unmanaged<CFError>?
// Private key parameters
var privateKeyParams: [String: Any] = [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: name
]
// If we are using a biometric sensor to access the key, we need to create an SecAccessControl instance.
if authenticationRequired != nil
{
guard let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, authenticationRequired!, &error) else
{
return false
}
privateKeyParams[kSecAttrAccessControl as String] = access
}
// Global parameters for our key generation
let parameters: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: privateKeyParams
]
// Generate the keys.
guard let privateKey = SecKeyCreateRandomKey(parameters as CFDictionary, &error) else
{
return false
}
// Private key created!
return true
}This is the code to sign the data that should prompt for the biometry sensor (Touch ID or Face ID).public static func sign(using name: String, value: String, localizedReason: String? = nil, base64EncodingOptions: Data.Base64EncodingOptions = []) -> String?
{
guard !name.isEmpty else
{
return nil
}
guard !value.isEmpty else
{
return nil
}
// Check if the private key exists in the chain, otherwise return
guard let privateKey: SecKey = getPrivateKey(name, localizedReason: localizedReason ?? "") else
{
return nil
}
let data = value.data(using: .utf8)!
var error: Unmanaged<CFError>?
guard let signedData = SecKeyCreateSignature(privateKey,
rsaSignatureMessagePKCS1v15SHA512,
data as CFData,
&error) as Data? else
{
return nil
}
return signedData.base64EncodedString(options: base64EncodingOptions)
}
fileprivate static func getPrivateKey(_ name: String, localizedReason: String) -> SecKey?
{
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrApplicationTag as String: name,
kSecReturnRef as String: true,
kSecUseOperationPrompt as String : localizedReason
]
var item: CFTypeRef? = nil
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else
{
if status == errSecUserCanceled
{
print("\tError: Accessing private key failed: The user cancelled (%@).", "\(status)")
}
else if status == errSecDuplicateItem
{
print("\tError: The specified item already exists in the keychain (%@).", "\(status)")
}
else if status == errSecItemNotFound
{
print("\tError: The specified item could not be found in the keychain (%@).", "\(status)")
}
else if status == errSecInvalidItemRef
{
print("\tError: The specified item is no longer valid. It may have been deleted from the keychain (%@).", "\(status)")
}
else
{
print("\tError: Accessing private key failed (%@).", "\(status)")
}
return nil
}
return (item as! SecKey)
}Then in my app, I would simply call guard let result = sign("mykey", "helloworld") else
{
print("failed to sign")
return
}
print(result)So the getPrivateKey function is the one that calls SecKeyCopyingMatching, the 3 methods are in a helper class; what's the best approach to reliabably display the biometry dialog?Thanks
Hi
In this snippet, I've already added a button, the onDisplay action simply adds another UIButton with an addAction event to the view controller
@IBAction func onDisplay(_ sender: UIButton) {
let semaphore = DispatchSemaphore(value: 1)
var didComplete = false
addButton("hello world") { result in
print("result: \(result)")
didComplete = result
semaphore.signal()
}
if semaphore.wait(timeout: .now() + 5) == .timedOut {
print("timeout: \(didComplete)")
}
print("didComplete: \(didComplete)")
}
func addButton(_ message: String, completion: @escaping (Bool) -> Void) {
let button = UIButton(type: .system)
button.frame = CGRect(x: 100, y: 100, width: 100, height: 40)
button.backgroundColor = .systemBlue
button.setTitle(message, for: .normal)
button.titleLabel?.tintColor = .white
button.addAction(UIAction(handler: { action in
completion(true)
}), for: .touchUpInside)
view.addSubview(button)
}
What I'm expecting to happen, is the new "hello world" button get created (which it does). If the user does nothing for 5 seconds (semaphore timeout), then print timeout: false.
If the user tapped the button, this would print result: true in the completion closure.
But what happens when I don't touch the button is didComplete: false prints out (the last line) and the semaphore.wait never gets invoked.
How do I make the semaphore execute it's wait operation? Been struggling with this for days 😞
Hi
I want to prevent files stored in my apps applicationSupportDirectory from being transferred when a user sets up a new device. Is there any guidance, an event to subscribe to that can be implemented to handle this?
Thanks
HiWhat is the best way to determine if an iPhone or iPad has a biometry sensor? The user may or may not have registered their face or fingerprint, regardless I would like to know what biometry sensor is on the device. My app targets iOS 11, so it has to be either Touch or Face.Many thanksCraig