WWDC 2021 Day 1

I never learn. Each year I love the start of WWDC: The excitement of what is new, the demos of how Apple sees features working, and the inevitable download of new tools, apps, and operating systems. And this year, like WWDC19, something goes wrong.

I picked up the new iPad Pro 11inch (with the M1), and have made sure that I don’t have any work related content. It is my test device. I expect to lose data… And like two years ago, when macOS bricked my MacBook Pro, this year my brand new iPad Pro went into a reboot loop on install.

I am hoping that the iPad Recovery mode, will allow me install the restore image, if not, I will back level to iOS 14. And try again. So far, I am not frustrated, more disappointed. I have raised a feedback, but I don’t think I should wait for them to come back (usually after the next beta). More to come.

However, I think WWDC has already announced a few key things that I am very excited about, let me count the ways.

  1. TestFlight for the Mac. Finally able to distribute my beta app to testers, who want to play with the Mac version. (I know I could distribute it normally, but it is much easier to distribute via TestFlight on iOS, WatchOS, iPadOS and tvOS – so this is good news).
  2. App Development on the iPad. This is THE reason I brought the new iPad Pro. Apple announced that they will be releasing a new version of Swift Playgrounds which will allow you to develop iOS and iPadOS apps on your iPad and submit them to the App Store. The App is not there yet, but I can’t wait.
  3. SharePlay – for years, many different tools and platforms have tried create shared viewing for TV and Movies. I think that Apple has figured it out. And now that FaceTime is allowing screen share, I may be able to switch to FaceTime to do my remote support of my parents and mother-in-law’s computer issues. We shall see.

This week will be very interesting to review all the details over the next 200 videos.

Getting ready for WWDC 2021

While sitting at home and watching a live streams for 8 hours a day all week, is not the same as being at WWDC in person, I am really looking forward to see what Apple introduces this year. To prepare this year, I am doing the same as I did last year. I have setup a new boot partition on my development MacBook Pro.

After the horrible experience of installing the latest beta on my main machine at WWDC 2019, and having it go bad. I had to ultimately get a lab session to install Developer Beta 1 from one of the installation engineers. They had a SSD that wiped the machine clean, and installed a pristine macOS, as if Developer Beta 1 came on my machine from the factory. I have created a second Boot partition with the latest Big Sur installed, iCloud turned off, and Xcode installed. My goal is to install the beta and the latest Xcode beta on this partition next Monday after the keynote.

I used this same technique last year, and it really made a difference in the stability of working with the developer betas over the summer.

I have also taken all my day job content off of my iPad Pro, and will be removing it from my iPhone later this week. Minimizing the number of variables on the machines make it much easier to experience the ups and downs of the “summer of fun.” I love the fact that the team over at the Upgrade podcast call this time the “summer or fun”, as it really is that. Getting to play with the various new features that come out over the summer, is a great way to learn.

WWDC 2021 Prediction

Wow, it’s already the end of May, and my last blog post was in February. Time sure flies when you work from home. Before WWDC happens this year I wanted to document my expectation. I talked about it during my podcast a few weeks ago, and even went out and bought a new iPad Pro – hoping that I am right.

While it would be very cool to run macOS on the iPad, I don’t think that is what will happened. Nor do I think Xcode will be delivered. But what I do think is going to happen is that we are going to say a flip from the Xcode Previews app. Last year – Apple introduced this app to allow your to preview your SwiftUI designs quickly and easily. I believe that this year, Apple will finally enable (at least on the iPad), on device UX design, which generates SwiftUI code for you in an attached Xcode instance running on a Mac.

The main reason that I don’t think that Apple won’t let you do full development is that the Mac is a premium desktop or laptop. And you pay that premium. If developers could do all their work on the iPad, then there would be a large drop off of Mac revenue. And with the new M1 MacBook Pro being rumored (14 and 16 inch models), there’s a lot of money on the table for Apple to make in 2021 and 2022.

What are your WWDC predictions.

Wasted Time Updates!

This past weekend I released updates to both WastedTime and WastedTime Watch. For WastedTime it was a minor bug fix, you can no longer end a meeting before it starts. Who would have thought of that one?!

For WastedTime Watch, this was the total re-write I was working on. It is now much more colorful – matching the design of both the TV and iOS versions. But it also ads support for Watch Complications.

I am still experimenting with the complications, as there is so little room to put things, but I thought I would get it out there and see what people thing. So if you don’t mind, drop me a review over on the App Store. Thanks

WastedTime Watch – Progress

Ok, I had basically gotten frustrated trying to change WastedTime Watch to a fully SwiftUI based app. Apple changed the App Entry point which had completely changed how the AppDelegate function worked. I thought this app was the simplest of my apps, so I could easily convert it to the new Swift Architecture. Boy was I mistaken.

In September, after banging my head against the wall for days, I posted the problem on StackOverflow, and walked away. Over the last few months, I got a coupled of suggestions, but severally figured out how to address the problems.

The net of the problem was I could not create my start up objects multiple times, but always ended up with a nil object. The basic startup of the application had changed, and I was not following it.

Today while trying another suggestion, I finally realized that the problem was (here comes the big surprising reveal) – Me! I had been using a local object instead of the delegate I created. here’s the basic structure of the now working code!

//
//  WastedTimeWatchApp.swift
//  TestMe WatchKit Extension
//
//  Created by Michael Rowe on 9/22/20.
//  Copyright © 2020 Michael Rowe. All rights reserved.
//

import SwiftUI

struct DelegateKey: EnvironmentKey {
    typealias Value = ExtensionDelegate
    static let defaultValue: ExtensionDelegate = ExtensionDelegate()
}

extension EnvironmentValues {
    var extensionDelegate: DelegateKey.Value {
        get {
            return self[DelegateKey.self]
        }
        set {
            self[DelegateKey.self] = newValue
        }
    }
}

@main
struct WastedTimeWatchApp: App {
    
    @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var delegate
    
    let prefs:UserDefaults = UserDefaults(suiteName: suiteName)!
    
    @SceneBuilder var body: some Scene {
        WindowGroup {
            NavigationView {
                ContentView()
                    .environment(\.extensionDelegate, delegate)
            }
        }
        WKNotificationScene(controller: NotificationController.self, category: "myCategory")
    }
}

class ExtensionDelegate: NSObject, WKExtensionDelegate, ObservableObject {
    
    @Environment(\.extensionDelegate) static var shared

    var meetingSetup: MeetingSetup!
    var meetingStatistics:  MeetingStatistics!
    var meeting: Meeting!
    
    func applicationDidFinishLaunching() {
        // Perform any final initialization of your application.
        print("applicationDidFinishLaunching for watchOS")
        ExtensionDelegate.shared.meetingSetup = MeetingSetup()
        ExtensionDelegate.shared.meetingStatistics = MeetingStatistics()
        ExtensionDelegate.shared.meeting = Meeting()
    }
}

The secrete ended up being that I had been using self.meeting = Meeting() in applicationDidFinishLaunch which was never actually exposed to the rest of the program. Now I should able to clean up a few simple problems in the views, and release version 2.0 of Wasted Time for the Watch

Holiday Card App Update

Well, it has been three years in the making, and I am finally starting to feel that this app may be ready. I’ve not had much time to work on it over the three years, but I’ve used this app to learn CoreData and CloudKit. On Christmas Day, I finally broke through and fixed my last MAJOR problem with CloudKit.

You see, I’ve been using this app to capture what cards I sent to which person. I’ve been storing the images locally in the app’s Documents folder, but I could not figure out how to easily get this to Sync. So I wanted to move the pictures into CoreData and then use CloudKit to sync the data between devices.

When I first started really working on this part over the US Thanksgiving holiday, I kept having issues where the app and CloudKit would crash. This only happened when I put on my devices, which had three years of card data! The simulator actually worked fine. I ended up learning how to finally use Instruments, and saw that I had two major problems. First was a memory leak. I had used sample code from https://www.raywenderlich.com to become a Transformable data type. What this does is make it so CoreData handles storing data in binary format, but your program just uses the appropriate format for the data. In this case a UIImage. Unfortunately this code didn’t free memory correctly, I fixed this by placing the code in a autoreleasepool{}. This means that when you program moves past that line in the code, it will release the memory allocated during the code. This solved part of the problem – no more crashes!

However, the program still didn’t sync the data. Looking in instruments again, I saw that CloudKit sync was crashing a lot too. I had converted the UIImage to a PNG data type. This is a lossless compressed file format, and as I’ve improved my iPhone over the three years the pictures had ballooned to about 30-40MB each! This is bigger than my first ever hard drive back in the late 80’s. (A 20mb hard drive which held all my programs, games and data at the time, with room to spare!).

So I ended up changing the image to a JPEG, using the least compression it would do, and now I have pictures in the 2-4mb range. Problem solved! All the images for the last three years have synced into CoreData and via CloudKit to my secondary device. Now I just need to wait for the App Store team to get back from vacation, and I will release the app to my Alpha testers!

Progress!

Wasted Time for Mac update

With the upcoming release of BigSur, I removed the Mac version of Wasted Time from the App Store a few weeks back. In the mean time the testing of the iOS version on the Mac has continued to be successful.

I have run into a few problems with minor new updates, since the Mac Version was a catalyst app, but had to be submitted on the store as a separate version. I am hoping that with the new Big Sur release coming any day, people can just use the iPad version. We shall see.

Temporarily removing macOS version of Wasted Time

One of the real benefits for users coming with macOS 11 and iOS 14, et. al. is that developers can now activate bundles for an app. I am working to create a bundle that will enable an iOS user to use Wasted Time on iOS, iPadOS, macOS, and tvOS all with one App Store purchase. Of course, my app is free, so it’s really a way for more people to leverage the app.

To do that, I have to take down my existing macOS version from the App Store. Given I’ve had very little uptake, I don’t think it’s a big deal. Hopefully existing users won’t have it taken off their machines. If it does, I hope the delay for the release of macOS 11 won’t be too long.

Adding Menus and Keyboard Shortcuts

As I continue to work on Wasted Time, I now have added working keyboard shortcuts, and menu items to both the iPad and macOS versions. I can’t wait for Big Sur and iPadOS 14 to come up so I can release these to the world.

First a few screenshots

The code ended up not being to difficult, once I figured it out. Of course, that is almost always the case. Moving from all the programming habits I developed in college and the few years that I made my living as developer, to modern languages and architectures, has been hard. While I fully understand the concepts, putting it in practice can be difficult. So let’s look at the code behind the Menu Builder function in Swift:

    override func buildMenu(with builder: UIMenuBuilder) {
        super.buildMenu(with: builder)
        
        // Ensure that the builder is modifying the menu bar system.
        guard builder.system == UIMenuSystem.main else { return }
        
        // Meeting Menu
        let advanceMeetingStatus = UIKeyCommand(title: "Advance Meeting Status", action: #selector(handleKeyAdvanceMeetingStatus(sender:)), input: UIKeyCommand.inputRightArrow, modifierFlags: .alternate, propertyList: UIKeyCommand.inputRightArrow)
        let incrementParticipants = UIKeyCommand(title: "Increment Participants", action: #selector(handleKeyIncrementParticipants(sender:)), input: UIKeyCommand.inputUpArrow, modifierFlags: .alternate, propertyList: UIKeyCommand.inputUpArrow)
        let decrementParticipants = UIKeyCommand(title: "Decrement Participants", action: #selector(handleKeyDecrementParticipants(sender:)), input: UIKeyCommand.inputDownArrow, modifierFlags: .alternate, propertyList: UIKeyCommand.inputDownArrow)
        let cancelMeeting = UIKeyCommand(title: "End Meeting Immediately", action: #selector(handleKeyCancelMeeting(sender:)), input: UIKeyCommand.inputLeftArrow, modifierFlags: .alternate, propertyList: UIKeyCommand.inputLeftArrow)
       
        let advanceMeetingItem = UIMenu(title: "Advance Meeting", image: nil, identifier: UIMenu.Identifier("advanceMeetingStatus"), options: .displayInline, children: [advanceMeetingStatus])
        let incrementParticipantsItem = UIMenu(title: "Add Participants", image: nil, identifier: UIMenu.Identifier("incrementParticipants"), options: .displayInline, children: [incrementParticipants])
        let decrementParticipantsItem = UIMenu(title: "Remove Participants", image: nil, identifier: UIMenu.Identifier("decrementParticipants"), options: .displayInline, children: [decrementParticipants])
        let cancelMeetingItem = UIMenu(title: "Cancel Meeting", image: nil, identifier: UIMenu.Identifier("cancelMeeting"), options: .displayInline, children: [cancelMeeting])
        
        
        let meetingMenu = UIMenu(title: "Meeting", children: [advanceMeetingItem,incrementParticipantsItem,decrementParticipantsItem,cancelMeetingItem])
       
        // View Menu
        let meetingView = UIKeyCommand(title: "Meeting View", action: #selector(handleKeyCommand1(sender:)), input: "1", modifierFlags: .command, propertyList: 1)
        let setupView = UIKeyCommand(title: "Setup View", action: #selector(handleKeyCommand2(sender:)), input: "2", modifierFlags: .command, propertyList: 2)
        let totalsView = UIKeyCommand(title: "Totals View", action: #selector(handleKeyCommand3(sender:)), input: "3", modifierFlags: .command, propertyList: 3)
        let helpView = UIKeyCommand(title: "Help View", action: #selector(handleKeyCommand4(sender:)), input: "4", modifierFlags: .command, propertyList: 4)
        
        let meetingViewItem = UIMenu(title: "Meeting View", image: nil, identifier: UIMenu.Identifier("meetingView"), options: .displayInline, children: [meetingView])
        let setupViewItem = UIMenu(title: "Setup View", image: nil, identifier: UIMenu.Identifier("setupView"), options: .displayInline, children: [setupView])
        let totalsViewItem = UIMenu(title: "Totals View", image: nil, identifier: UIMenu.Identifier("totalsView"), options: .displayInline, children: [totalsView])
        let helpViewItem = UIMenu(title: "Help View", image: nil, identifier: UIMenu.Identifier("helpView"), options: .displayInline, children: [helpView])
        
        let tabsMenu = UIMenu(title: "Tabs", children: [meetingViewItem, setupViewItem, totalsViewItem, helpViewItem])
        
        builder.insertSibling(meetingMenu, afterMenu: .edit)
        builder.insertSibling(tabsMenu, afterMenu: .edit)
        builder.remove(menu: .file)
        builder.remove(menu: .edit)
        builder.remove(menu: .format)
        builder.remove(menu: .help)
        
    }

The net of this code is to build up the actions, then the items, and then attach the items to a new UIMenu object. I add the various items ad children to the UIMenu, and then insert the UIMenu after the file menu. Since I don’t use the file menu, I then remove it, along with edit, format and help.

Not too hard.