StoreKit2 With XCode

When testing StoreKit connected directly to XCode, you need to have your .storekit file open and then use the debug menu to test purchases.

This is the first time I realized that the menus changed what they have in them dependent on what file you have open. This also means when you go under Editor menu that also changes too.

This caused me to go in circles on why clearing Tester purchases in App Store Connect wasn’t working.

Bowling Score Tracker

My girlfriend is part of a bowling league and every week she writes her scores down on a piece of paper. After seeing her do this for many seasons of bowling I’ve decided I would build her a simple mobile application that she can enter these scores into.

If you have a iPhone with iOS 15.6 or higher you can direct message me on twitter (with your email address, and first and last name) and I’ll invite you to the TestFlight. I’m planning on running this TestFlight beta until mid November 2022.

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.