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.

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)"
}

Slide Out Menu for iOS using SwiftUI

This week I’ve been working on prototyping a slide out menu, or as most people know it as the hamburger menu. This is a very useful navigation system for apps once the scale past 5 “systems” or tabs.

This system has to wrap the main content view inside of a wrapper view in order to handle the requirements of the system.

The requirements of the system are as follows

  • It must use a navigation view to host a toolbar button that will allow the user to open the side menu.
  • The side menu must contain a list, that the entire width of can be tapped on causing the content view to change to the newly assigned view, and closes the side menu.
  • The side menu must be able to open and close using swipe gestures.
  • When closed, swipe from left to right must open the side menu.
  • When open, swipe from right to left must close the side menu.
  • All other swipe gestures must be ignored.
  • Tapping outside of the slide out menu must also close the menu.

Before we get too into the details we need a couple of things in the system to handle data storage and management. First we need a Observable Object to store the content view inside of.

class NavigationCoordinator: ObservableObject {
fileprivate var screen: AnyView = AnyView(EmptyView())
func show<V: View>(_ view: V) {
screen = AnyView(view)
}
}

The next thing we need is an enum for the swipe gesture directions. Since we only care about left and right those are the only ones I’ve included.

enum DragDirection {
case left
case right
case none
}

Now to get onto the creating of the actual views, we have to have a Side Menu. This is effectively just the container for the Slide Out navigation and animation.

struct SideMenu: View {
	let width: CGFloat
	let isOpen: Bool
	let menuClose: () -> Void
	let navigationCoordinator: NavigationCoordinator
	
	var body: some View {
		ZStack {
			// Code here
			GeometryReader { _ in
				EmptyView()
			}
			.background(Color.gray.opacity(0.3))
			.opacity(self.isOpen ? 1.0 : 0.0)
			.animation(Animation.easeIn.delay(0.25))
			.onTapGesture {
				self.menuClose()
			}
			
			HStack {
				MenuContent(navigationCoordinator: navigationCoordinator, menuClose: menuClose)
				.frame(width: self.width)
				.background(Color.white)
				.offset(x: self.isOpen ? 0 : -self.width)
				.animation(.default)
				
				Spacer()
			}
		}
	}
}

Next up is the content of the Slide Out Menu. Basically it’s just a list with some changes to make it look right in this context and to trigger the navigation on tap.

struct MenuContent: View {
	
	let navigationCoordinator: NavigationCoordinator
	let menuClose: () -> Void
	
	
	var body: some View {
		List {
			
			ZStack {
				VStack(alignment: .leading) {
					HStack {
						Text("My Profile")
						Spacer()
					}
					.frame(maxWidth: .infinity)
					.contentShape(Rectangle())
				}
				.frame(maxWidth: .infinity)
				.contentShape(Rectangle())
			}
			.contentShape(Rectangle())
			.listRowBackground(Color.red)
			.frame(maxWidth: .infinity)
			.onTapGesture {
				print("My Profile")
				navigationCoordinator.show(Text("My Profile"))
				menuClose()
			}
			
			ZStack {
				VStack(alignment: .leading) {
					HStack {
						Text("Posts")
						Spacer()
					}
					.frame(maxWidth: .infinity)
					.contentShape(Rectangle())
				}
				.frame(maxWidth: .infinity)
				.contentShape(Rectangle())
			}
			.contentShape(Rectangle())
			.listRowBackground(Color.teal)
			.frame(maxWidth: .infinity)
			.onTapGesture {
				print("Posts")
				navigationCoordinator.show(Text("Posts"))
				menuClose()
			}
			
			ZStack {
				VStack(alignment: .leading) {
					HStack {
						Text("Logout")
						Spacer()
					}
					.frame(maxWidth: .infinity)
					.contentShape(Rectangle())
				}
				.frame(maxWidth: .infinity)
				.contentShape(Rectangle())
			}
			.contentShape(Rectangle())
			.listRowBackground(Color.indigo)
			.frame(maxWidth: .infinity)
			.onTapGesture {
				print("Logout")
				navigationCoordinator.show(Text("Logout"))
				menuClose()
			}
		} // end list
	}
}

Now onto the last big piece of this puzzle. This is the actual Content View, or in this case the main view of the application. This is where we handle the gestures and the navigation view.

struct ContentView: View {
	
	@State var coordinator: NavigationCoordinator = NavigationCoordinator()
	public init(@ViewBuilder content: () -> AnyView) {
		coordinator.show(content())
	}
	public init() {
		coordinator.show(EmptyView())
	}

	@State var menuOpen: Bool = false
	
	var body: some View {
		ZStack {
			NavigationView {
				HStack {
					coordinator.screen
				}
				.navigationTitle("Add Location")
				.navigationViewStyle(.automatic)
				.navigationBarTitleDisplayMode(.inline)
				.toolbar {
					ToolbarItem(placement: .navigationBarLeading) {
						Button(action: {
							openMenu()
						}) {
							Image(systemName: "line.3.horizontal")
						}
					}
				} // end toolbar
			}
			.navigationViewStyle(.stack)
			
			SideMenu(
				width: 270,
				isOpen: menuOpen,
				menuClose: openMenu,
				navigationCoordinator: coordinator)
		}
		.gesture(
			DragGesture(minimumDistance: 60)
			.onEnded { drag in
                // TODO: fix the gestures that are supposed to be ignored.
				let direction = drag.predictedEndLocation.x > drag.startLocation.x ? DragDirection.right : DragDirection.left
				switch direction {
				case .left:
					self.menuOpen = false
				case .right:
					self.menuOpen = true
				case .none:
					break
				}
			}
		) // end gesture
	}
	
	func openMenu() {
		self.menuOpen.toggle()
	}
}

Hope someone finds this useful and helps them build some awesome applications.

Converting a callback function to async await in Swift

Occasionally you will need to convert a callback based function to an async/await function. One example of this is below, calculating a map route appears to be only available as a callback based one. Below is an example of how to convert it to an async function. This code is actually used in Weather Driver.

func calculateSubRoute(route: MKRoute, step: MKRoute.Step) async throws -> MKDirections.Response? {
	return try await withCheckedThrowingContinuation { continuation in
		let request = MKDirections.Request()
		request.source = MKMapItem(placemark: MKPlacemark(coordinate: route.steps.first!.polyline.coordinate))
		request.destination = MKMapItem(placemark: MKPlacemark(coordinate: step.polyline.coordinate))
		request.requestsAlternateRoutes = true
		request.transportType = .automobile

		let directions = MKDirections(request: request)
		directions.calculate() { response, error in
			if error != nil {
				print(error)
				continuation.resume(throwing: error!)
				return
			}
			continuation.resume(returning: response)
		}
	}
}

Sharpening My Tools Part 2

Developing an iOS application is full of small bits of repetitive tasks. When you develop User Interface in code, you must create and handle constraints in code. Depending on what you have to do this could become extremely repetitive and complicated. You can write each constraint yourself, but this gets time consuming and could become messy and confusing.

I decided to create a function that accepts the child view and the parent LayoutGuide, this solved the issue for if you want the child to fill the parent. It however doesn’t solve all issues, example when you don’t want the bottom side to match the parent. Initially I decided to allow booleans to be passed in for each constraint to disable them and still allow them to be manual.

However this still left my code messy and complicated. So I decided to make it more inclusive of the other needs. In the next version I decided to allow up to 3 parameters for each of the side constraints, one named for the side to enable and disable it, a constant to allow a distance from the anchor, and a target for those times when it shouldn’t be pinned to the parent element.

However this wasn’t quite enough I needed two other capabilities. The first, one I needed the ability to enable and disable constraints inside of other classes. I return a class with they NSLayoutConstraint for each constraint allowing other classes to access these. The other major capability is the ability to have width and height anchor constraint, therefore I allow these constants to be passed in as parameters.

This utility class has cleaned up my code quite a bit, I hope others will find it useful. It is available on Github at https://github.com/all12jus/ConstraintUtils.swift.

Sharpening My Tool Part 1

I’ve been working on a full fledged pdf reader application for iOS. During the process of developing for this I have wanted to use Icons from Font Awesome; as buttons in the app.

Apple’s iOS doesn’t like SVG files or the font awesome font files, to work around this I’ve decided to make a tool that will convert Font Awesome SVG files into image files that can then be used directly in the application.

The tool that I wrote is a command line tool that generates three image files that corresponds to the 1x, 2x, and 3x that apps require. However this isn’t quite enough to finish the process, in the normal process you must then create an imageset in xCode then drag in the images. We can automate this process by creating a specific directory structure and json files to tell xCode where the files are.

The source code of this script is available on GitHub. It can be modified for use with any SVG image set, I use it with both Font Awesome Free Version and Font Awesome Pro. The source can be downloaded from https://github.com/all12jus/SVGToXcassets.

Weather Driver

My current personal project is codenamed Weather Driver. This is an iOS application targeted at Truck Drivers and other people who travel on the road. This application will allow them to better plan their trip around the weather. You will be able to enter your destination, chose a route, and then see the weather at various points thru-out the route.

This uses WeatherKit API from Apple, along with MapKit. This will have a server side component to handle getting the weather from Apple. The server side portion of this application will be written in Node.js.

How I decided to build this application, I drove 18-wheel for about 3 years. Part of my job as a Truck Driver was to plan my route based on the weather, I always found it tedious to check both the route I had to travel and the weather at multiple locations along the route.

I’ve been thinking about this project for over a year and finally have the tools to build it. Follow me as I build this project out and bring it to market. @JJAllenTech