When sharing data between targets using UserDefaults

When you have to use UserDefaults to share data between a core app and a widget you have to do a couple of things.

Inside of the project Signing & Capabilities use the Add Capabilities button to add App Group. You need to do this while selecting both the app target and the widget target.

extension UserDefaults {
    static let custom: UserDefaults = {
        let appGroupId = "group.tech.justins.BowlingScoreTracker"
        return UserDefaults(suiteName: appGroupId)!
    }()
}

Now anywhere in the app where you set or get from UserDefaults just use .custom instead of .default. And that will synchronize across both of them.

Advanced SwiftUI Stepper

Apple’s built in stepper allows you to only have one step increment value, I’ve designed on that allows for two different increment values. Initial version supported 1 and 10 for the step values and is tied to an integer.

import SwiftUI

struct ContentView: View {
    @State var score: Int = 0
    var min: Int = 0
    var max: Int = 100
    var template: String = "Score: %@"
    
    var body: some View {
        
        Form {
            Text("Expanded Stepper Example")
            HStack {
                Text(String(format: template, score.formatted()))
                Spacer()
                Button("-10") {
                    score = score - 10
                }
                .disabled(score - 10 < min)
                .buttonStyle(BorderlessButtonStyle())
                .padding(.horizontal, 4)
                
                Button("-1") {
                    score = score - 1
                }
                .disabled(score - 1 < min)
                .buttonStyle(BorderlessButtonStyle())
                .padding(.horizontal, 4)
                
                Button("+1") {
                    score = score + 1
                }
                .disabled(score + 1 > max)
                .buttonStyle(BorderlessButtonStyle())
                .padding(.horizontal, 4)
                
                Button("+10") {
                    score = score + 10
                }
                .disabled(score + 10 > max)
                .buttonStyle(BorderlessButtonStyle())
                .padding(.horizontal, 4)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This of course is the first revision, and it isn’t very reusable in the current state. So the next step was to make it more reusable.

import SwiftUI


struct AdvancedStepper: View {
    var value: Binding<Int>
    var min: Int
    var max: Int
    var template: String
    
    var body: some View {
        HStack {
            Text(String(format: template, value.wrappedValue.formatted()))
            Spacer()
            Button("-10") {
                value.wrappedValue = value.wrappedValue - 10
            }
            .disabled(value.wrappedValue - 10 < min)
            .buttonStyle(BorderlessButtonStyle())
            .padding(.horizontal, 4)
            
            Button("-1") {
                value.wrappedValue = value.wrappedValue - 1
            }
            .disabled(value.wrappedValue - 1 < min)
            .buttonStyle(BorderlessButtonStyle())
            .padding(.horizontal, 4)
            
            Button("+1") {
                value.wrappedValue = value.wrappedValue + 1
            }
            .disabled(value.wrappedValue + 1 > max)
            .buttonStyle(BorderlessButtonStyle())
            .padding(.horizontal, 4)
            
            Button("+10") {
                value.wrappedValue = value.wrappedValue + 10
            }
            .disabled(value.wrappedValue + 10 > max)
            .buttonStyle(BorderlessButtonStyle())
            .padding(.horizontal, 4)
        }
    }
}

struct ContentView: View {
    @State var score: Int = 0

    var body: some View {
        Form {
            Text("Expanded Stepper Example")
            AdvancedStepper(value: $score, min: 0, max: 10, template: "Score: %@")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I think I’m happy enough with this one for now. Of course I could take this further and make the step amounts customizable by the usage location.

MUI React Progress Bar

When building a page with a Linear Progress bar, I thought it should have been partially filled and it wasn’t filled at all. Turns out it expected value to be between 0-100 and I was assuming it would have been 0-1. Best to check the documentation over just guessing, but we all know developers hate reading the documentation. So best do some tests to see which range it expects.

Debugging in PHP

When you want to debug certain sections of code with in an environment without restarting Apache or NGINX, there’s a template I use for displaying print messages.

ini_set('display_errors', 1);

echo "<pre>".json_encode($VALUE, JSON_PRETTY_PRINT)."</pre>";

ini_set('display_errors', 0);

Swift Temperature Format Customization

When you use Measurement format function in Swift it will always format the temperature to the format of the locale of the device. This is not the experience I wanted in my application. I wanted to provide the user an option to chose which scale they wanted to use, so I had to override the format operation. I used the following function to make this happen.

func formatTemperature(_ temperature: Double, _ scale: UnitTemperature) -> String {
	let source = Measurement(value:temperature, unit: UnitTemperature.celsius)
	let converted = source.converted(to: scale)
	return "\(String(format: "%.1f", converted.value))\(converted.unit.symbol)"
}

Error Handling

Some of the biggest things that bug me as a user of software is how they handle errors. Apple has been known for silently handling errors and not telling users that something went wrong, this is bad. Another example of bad user handling is how Git Kraken handles theirs, they have a toast in the lower left that disappears after a duration of time. Both of these are really frustrating to the user of the software.

The best way to handle errors is to provide clear and copy-able error messages for your user. You could provide this using an alert, clearly visible logs, or a (persistent until dismissed) toast message in the corner. How ever you do this, provide a way that the user can take the error to Google afterwards. I prefer the alert method so that I am forced to acknowledge that an error has occurred.

PHP Ini File

Sometimes when you are using php you need to change some of the settings, this can be done by using the ini file.

Before you change anything in the ini file you should check the phpinfo. This can be done by creating a file containing <?php phpinfo(); ?> and then navigating to the path for that file in a browser.

Once you have confirmed where the php ini file is, make your changes. To apply these changes you will have to restart apache. The code to do this depends on which platform you are running. One of the common ones can be done by systemctl restart apache2. Hope this helps someone.

Code Style Tips

When you write if/else statements please put your else block on a new line not on the same line. The following doesn’t work with certain editor for code block collapsing.

if(x==1){ 
    console.log("Blah");
} else {
    console.log("bah");
}

Do the following instead.

if (x == 1) 
{
    console.log("Blah");
} 
else 
{
    console.log("bah");
}

OR

if (x == 1) {
    console.log("Blah");
} 
else {
    console.log("bah");
}

The second way allows those of us who use code collapse features inside of editors to collapse each part of the if/else. Thanks.