While updating Kotoba from a hospital bed, I encountered a problem while presenting a UIKit view from SwitfUI on the iPhone Duo – it totally stumped me. It’s one of those things that make sense once you discover the solution, but is unintuitive until then.
The main view in the app is written in SwiftUI and uses a couple of UIViewControllerRepresentable instances as sheets that are presented over the primary user interface. It looked like this:
The vertical navigation and scroll indicators were both inset from the trailing safe area. That’s not supposed to happen.
This is a simple table view with a sharing icon in the left bar button item and a Done button in the right item. As basic as it gets in the land of UIKit.
I checked and double-checked the constraints on the views but no changes to the storyboard improved what I saw in the Simulator.
So I started to dig into the view that was presenting the view controller and immediately saw a problem. The table view is used as a UIViewControllerRepresentable from SwiftUI and its hosting view was applying safe area insets when my view controller was presented.
Luckily, the fix was simple: I just removed the safe area insets when the view is presented so they can be handled automatically (or otherwise) in the storyboard or viewDidLoad:
I can see why the safe areas are applied to the hosting view: if you are a presenting a view without a UINavigationController, it just works. If you are presenting a more complex hierarchy from SwiftUI, it takes a little more effort.
I’m writing this post from a hospital bed. And I couldn’t be happier.
You hear folks say chemotherapy sucks, but until you go through it yourself, you don’t really know how bad it can be. It remains a popular activity only because it’s better than the alternative.
My first exposure to chemotherapy was with this contraption: a fluoroscope.
Lovely to look at, less lovely to lie on.
Every week, they used this machine to tap into my spine, extract 5 mg of fluid for analysis, and replace it with 5 mg of methotrexate. There was only a local anesthetic during the procedure and the hardest part was knowing that there was a needle, turned up to eleven, going into my back.
It was afterwards that things got dicey: the nerves in your spine float in a thecal sac filled with spinal fluid that acts as a kind of insulator. Since we were exchanging the fluids, it was easy for the levels to get out of balance and for the nerves to react.
The result were the worst headaches I’ve ever experienced. The good news is that the remedy was simple: to lie flat on my back to ease the pressure gravity was putting on my spine.
That was also the bad news: many days were spent lying prone and trying to avoid boredom. Watching shows and scrolling timelines just doesn’t cut it when that’s all you can do.
Thankfully, the spinal headaches subsided quickly when the six-week course of lumbar punctures finished at the end of January. But my body began to face a new challenge: chemotherapy infusions of Bendamustine and Rituximab.
Everyone experiences chemotherapy differently. Just as every body is different, so is the reaction to the chemicals that are killing the cancer.
The first effect I noticed was in my tastebuds: everything I put in my mouth had a slightly metalic taste, so my eating habits changed. My beloved morning coffee was replaced by tea, food needed a lot less salt, and I started eating a blander diet (cottage cheese with berries remains a favorite).
Some people experience nausea and other gastric issues: luckily, I avoided that. I also didn’t have any hair loss, but my normally thick and curly hair lost its body and became lifeless: an external expression of what was happening to me internally.
As someone who’s always been active and strong, the biggest overall effect of chemo was losing muscle mass and suffering from overall fatigue. It felt very strange to be weak.
I was on a 28 day cycle for my infusions: during the first week I got pumped full of chemicals and then spent three weeks letting my body process them. One of the things they gave me with the infusion on a Tuesday was a dose of steroids that gives the body a boost during the initial onslaught. But by the weekend, that wore off and I felt like shit. It usually took until the middle of the second week before I started feeling human again.
I didn’t lose my appetite, but I wasn’t eating more than normal. The problem was my metabolism was working overtime and my calorie intake didn’t adjust. I lost 50 pounds over the course of a couple of months and eventually got to the point where I was having five meals a day. Too bad it all tasted bad.
This could also be a graph of my happiness.
One thing that I did to fight my diminished capacity was a daily walk. Anyone who exercises regularly knows that there are always plenty of reasons to avoid it: you didn’t sleep well, you have an important project to finish, or you’re sore from a previous workout. When you’re doing chemo, add feeling like absolute shit to that list.
I still had nerve damage in my spine, so my “walk” was more like a shuffle. Thankfully my wife found some awesome walking sticks to aid with my balance. They are clearly labelled “Not A Medical Device” but they are my most important medical device.
Winter sunsets, exercise, and walking sticks are good medicine.
My daily exercise was just like any gym workout: dread at the start, joy at the end. Sometimes there wasn’t much behind that joy: at my lowest point, a walk around the block on flat ground got my heart rate up to 160 bpm. Breathtaking, literally.
That low point was in March. In the week following my infusion, where I was already feeling like crap, I came down with a type of pneumonia that affects people with weakened immune systems. I had no strength and my blood oxygen level plummeted below 90%. Getting from the living room to the kitchen for a drink of water felt like running a marathon, and then my Apple Watch pinged to inform me that I was having an episode of atrial fibrillation. It was a lot and I honestly had thoughts of “is this how it ends?”
Thankfully, antibiotics battled the pneumonia quickly and the chemo effects subsided. My doctor offered to skip the next cycle of chemotherapy, but I decided to push on. There is only one way to be done with a thing.
Throughout all of the infusions, the nerve damage that occurred before the lymphoma discovery didn’t improve. As I said above, my gait and balance were severely compromised: going up and down stairs was particularly challenging. The tingling sensations on my backside and numbness near my abdomen became a bigger concern with each passing month. It wasn’t getting better and I feared it was a permanent condition.
I finished the last of my infusions in May and my body slowly started to recover. Food started tasting better, and my strength and stamina started to return. My daily walks got longer: first a quarter of a mile, then half a mile, then a mile, and then something fantastic happened.
At the end of a walk around our local state park, I went to grab something in the car glovebox. As I did this, I tilted my head forward and the back of my legs lit up with a feeling I had never felt. Something was changing in my spine: and after a couple of weeks it was clear that this was a sign that the nerves were fixing themselves.
Our guess is that the chemotherapy infusions, which attack white blood cells and other parts of the blood that aid in recovery, had retarded the healing in my nervous system. Now that my body had a chance to repair itself, it was.
I can now go up and down stairs without hanging on for dear life. I’m able to walk without aid, but I still like my walking sticks for longer outings since they let me push myself harder. I’ve also started working out in a gym with a trainer: I told him about my stair issues, and now he has me doing them backwards and by hopping on all fours. I am so happy to look like a doofus, and for the continued improvement I’m seeing with exercise.
My regained ability to walk and a stronger immune system allowed me to leave the house. My first night away from home was a short trip to Topanga Canyon to celebrate an August birthday:
All hail the sunflower goddess!
After that successful trip, we took a short boat ride to have a nice lunch and look at some kelp. It felt so good to be able to do normal things again.
A trip to visit The Blue Forest
One lingering part of my life that hasn’t been reestablished is swimming. All the chemotherapy that has been put into my body has been through a port catheter: a small device under my skin that hooks directly into my carotid artery. Getting an infection there would be a very bad thing, and water increases that risk: be it from ocean water full of living organisms or a swimming pool that kids pee in.
With the current El Niño conditions, that’s been a hard thing: the water is warm and “interesting”. My new gym also has a beautiful Olympic-sized pool for lap swimming. I’m happy to be in this hospital bed doing a final stint of chemotherapy because when I’m done, the port is removed.
This last procedure is to make sure there is no lymphoma in my brain fluids: not a difficult or dangerous treatment and the side effects are minimal. But it does require close monitoring because of the high dosages involved.
In six weeks, I’ll be done and spend the winter in that heated pool so that I can finally swim in my beloved Pacific next summer. The buoys are still there, even though the high surf this summer knocked one out!
Last year was hard, and it got worse over time. This year has also been hard, but it’s gotten better with each passing month. For that I’m grateful.
I’m also grateful for my wife Lauren: it’s impossible to imagine making it through all of this without her help, support, and encouragement. The silver lining in all this strife is that it’s made our relationship much stronger. Thanks sweetie!
As with my previous account, there’s an unwanted coda. No matter how hard I try, there’s a complication at the happy ending!
I had typed up everything above as the doctors and nurses prepared me for the first dose of methotrexate. It takes a while for your blood’s pH level to reach a point where the treatment is safe, so I spent much of the day lying in bed, peeing into urine cups, reminiscing about my year, and typing.
Around 10:30 PM, the medication was in my brain and I was trying to wind down for the night (easier said than done when part of the regimen includes steroids). While scrolling through Tapestry, I noticed quite a few posts about a newly announced PHP vulnerability. This caught my attention because we use it for our blogs, an ad server, delivering Kickstarter rewards, and a bunch of other small things.
(This is the point where family that doesn’t understand what I do for a living continues to not understand what I do for a living.)
It quickly became apparent that this was a big deal and something that could allow an attacker access to our server. I had to shut things down before my brain shut down. Thank goodness the steroids held out.
After a couple of hours, I had blocked access to PHP processes in every nook & cranny of our server. After sending a quick message to our Slack explaining how everything was broken, I collapsed.
The next day started with a head and server that were barely working. Our server was out of LTS, so the first order of business was to get it back onto the latest version of Ubuntu using hospital WiFi. It was a good thing my heart rate and blood pressure were being closely monitored.
(Thank goodness for Lish, which proved to be immune to hospital IT shutting down my ssh connection to accept terms & conditions every few hours. There’s also a “I should have my head examined” joke in here somewhere.)
If you noticed that the Iconfactory sites were kinda broken last week, now you know why. It’s because I was kinda broken last week.
Now I’m finishing this post from a comfy chair on our deck. Typing into the latest version of WordPress and PHP. And I couldn’t be happier.
On a Monday morning, I thought I’d take a crack at implementing tips in one of my apps. There were some gestures and other features that customers weren’t finding and explaining those things with tips would be helpful. I expected the task to take about a day.
It ended up taking a week.
Why so long?
Well, the first problem is when you watch the introduction video from WWDC ’23 you’ll see outdated code throughout the presentation. TipsCenter doesn’t exist. Configuration is completely different. And there is no mention of UIKit.
Then, when you look at the sample code, it’s all SwiftUI. At least the sample compiles and runs because it uses a completely different syntax than what was shown at WWDC.
But the fact remained: I had UIKit views where I wanted to display tips. And there wasn’t any information on how to accomplish that. Did Apple really release a framework that didn’t work on the code we’ve been crafting for the past 20 years?
Tips are something you want to add to existing code. And UIKit is the most likely case there: you’re not going to rewrite your views in SwiftUI just to explain some features. The biggest failure with the TipKit introduction is that it ignored the past and how apps have historically been built.
Eventually I stumbled upon TipUIPopoverViewController which inherits from UIViewController. That should work!
This is the point where you start to learn that a Tip is a dynamic state machine that’s mostly out of your control. My problem was understanding how that state machine interacted with my own code (and its state).
Eventually, you also learn that the Tip’s state is persisted. Until you realize that any changes you make are stored in a SQLite database, debugging is very confusing. It’s also easy for your own state to get out-of-sync with the Tip state: there is not a single source of truth.
Your first task will be to figure out how to get the Tip close buttons to work. The sample code for TipUIPopoverViewController hints at what you need to do: implement an asynchronous task that monitors the state of the tip. When that tip gets into the right state, it’s your job to both present and dismiss the popover view controller.
Unfortunately, that sample code doesn’t scale well. If you have a view with multiple tips, you’re going to be littering your code with tip instances, observation tasks, and popover controllers. It’s a mess and a clear sign that the TipKit developers didn’t think much about UIKit.
My solution is a TipPresenter class. It’s instantiated by your view controller, starts an observation task, and then presents or dismisses a popover as the Tip’s state changes. It significantly reduces the clutter, makes refactoring views/tips easier, and it even works from Objective-C code (yes, some of us still have that).
One thing to keep in mind when you’re using TipPresenter: the observation task is a strong reference with weak references to a view and controller. Make sure you call stop() as you clean up your view: if you’ve ever used a notification observer, you’ll know the pattern and why you need it :-)
Another pattern emerged once I had a convenient way to present tips: view updates and tip updates go hand in hand.
Most of our UIKit apps rely on a Model-View-Controller architecture. The model gets updated, changes propagate to the controller, which uses a view to display the new information. Every UIViewController has something like an updateView().
You’ll quickly find that your model changes will need something that moves state to the tip presenter. Whenever updateView() gets called, you’ll also call updateTips().
The TipKitHelper file contains the Tip definitions, the TipPresenters, and the configuration helper class (which also has Objective-C members). Also of note: this code is compatible with iOS 17 and later.
I’m not thrilled with the need to have separate implementations of the TipPresenter classes, but since Tip is a struct that can’t be bridged to Objective-C, it has to be that way.
To quickly find important stuff in the project, search for NOTE:. There is also a lot of debugLog() in the code to help you see what’s going on from the Xcode console.
As you get more familiar with TipKit, I highly recommend this article on Fatbobman’s Blog – it digs into the more advanced aspects of TipKit and includes an example of how to do an inline tip with UIView or NSView.
Armed with this code and information, adding TipKit to your UIKit app should take less than a week. Maybe even just a single day like I initially thought!
A few weeks ago, my iPhone started having a serious problem: it could no longer search for things. This bug affected the App Library, Mail, Notes, Messages, Settings, and more. Having a device that couldn’t find things was crippling.
All signs pointed to this being an issue with the Spotlight index on the device: the database was corrupted and unable to do queries or updates.
Of course I searched for clues on how to fix this issue and tried the following things:
Forced restart
Changing language and region settings
Toggling location services for Suggestions & Search
Reindexing All Items in CoreSpotlight using Developer Mode
Toggling the App Search settings
Updating iOS from 26.4.1 to 26.4.2
Toggling Siri on & off
Looking at Console output to check spotlightd for errors
All of the hints I saw online ended with “if this doesn’t work, just Reset All Settings”. When you go to do that, you’re presented with the following dialog:
I’ve been adjusting my settings on my phone since 2007 and honestly couldn’t imagine the fallout from resetting them to defaults. All I knew is that this would be incredibly disruptive to my life for several months as I stumbled over things that needed setup.
For example, would a nice evening in a restaurant with dim lighting be interrupted when I triple-clicked the side button to get the Magnifier to read the menu? Being taken out of the moment by tapping around in Settings was not something I wanted to risk.
That left me with one alternative: a full device backup and restore. I did this on my Mac to make it as speedy as possible, but the process still took a couple of hours. During all of which I was without my primary device.
Afterwards, there were immediately problems:
Apple Pay was reset on both my iPhone and Apple Watch, requiring me to update my drivers license and credit cards.
TestFlight builds were uninstalled: for a developer this is significant.
Logins to apps that don’t use the keychain didn’t work.
The 24 apps using FaceID needed setup again.
All hints in all apps were reset.
Some Safari content blockers weren’t working and needed updates.
All apps prompted for Allow Access on Local Network and other privacy features.
Apps on the Home Screen that required FaceID to open were reset.
Unlocking the iPhone with Apple Watch needed setup.
Developer Mode needed to be re-enabled.
In short, this took several days to sort out. And the entire time, it pissed me off because it was entirely avoidable.
On the Mac, there is a simple procedure to rebuild the Spotlight index. This same affordance is not available on iOS.
It just works, my ass.
Spotlight is a database that’s accessed by a lot of processes in a lot of different situations. It’s reasonable to expect that all this activity can uncover bugs that corrupt the index. The kinds of issues that are hard to reproduce, but easy to repair with a simple button labeled “Rebuild”.
Why doesn’t that button exist on iOS? Maybe it’s a product manager in denial. Maybe it’s because the Spotlight developers are always working on fresh database and don’t ever see the issue (my database had likely existed for months or even years).
Whatever the reason, there are plenty of YouTube videos and Apple Support discussions that show this issue is widespread. My proposal is to add a button to aid customers:
Put it in the Search category, since folks understand this issue as “search doesn’t work”. Do not bury it somewhere deep in the hierarchy because when you have a corrupt Spotlight index, you can’t search in Settings. It’s got to be easy to find that “Rebuild” button.
Add some context for anyone who uses it. The Mac support article mentions that “This can take some time, depending on the amount of information being indexed”.
Show some kind of progress indicator as the index rebuilds, as the Mac does.
One of the things I love doing is naming things. Whether it’s for products, or things that I encounter daily, it’s a habit.
Fifteen years ago, I started a list for our new dog, Pico. My wife and other family members joined in on the fun. Last December, we updated the note for the last time.
I’ve shared this list over the years and everyone gets a kick out of it, but the main reason I’m putting it up here is so I have a place to remember all the good times we had together.
The names are in chronological order, the photos are not.
Our first day together.
Snooty
Pico-bahn
Snoot Butt
Bud
Always on the lookout for suspicious activity.
Officer Pico Rivera, Dog Cop
Snootsicle Von Tootsicle
Struttin’ Von Buttin
Snootsie Von Tootsie
With his 10× doppelgänger at the dog park.
Mr. Toothpick McShivers
Piconator
Snoots
Squeeko
Snootsicle Bootscicle
Packed wet sand is the perfect medium to show off your skills.
Sandy Dandy
Sandicle Dandicle
Budly Studly
Always pay attention to nearby vehicles. Or take a nap.
Captain Snoot of the S.S.Picobahn
Barkey McLoudbutt
Power Snoots
Lord Voratio
Rooter
Barky Boy
Sir Barksalot
High Falutin’ Snootin’
The Force was definitely strong with this one.
Obiwan Snootobi
Turd Burgler
Snoot Doggy Dog
Sheik Yer Snooty
Sir Prancelot
Pink Butt
No shortage of freckles or desire to show them off.
Freckle Pecker – Deck Commander
Snooticus Tooticus
Bupster Pupster
Snootrition
Nudie Snoodie
Food Genius
Quicker Licker
Low tide is the best tide.
Moto Pico
Pink Gooba
A dog can only take so much hot and dusty trail, so hitch a ride.
Bud Bud
Bumpy Gut
Platemaster Supreme
Mister Bits
There was no limit to his licking abilities.
Dr. Bits, Molecular Foodologist
The Leaf Thief
Hellen Killer
Mister Biscuits
Snootrous Boutros-Ghali
All kinds of sizzle.
Little Sizzle (Rapper Name)
Lumpystiltskin
A grunt is the best way to express your unhappiness with a situation.
Grunty McRunty
Yawny (pronounced Yanni)
Mister Slurps
Hacky Jack
Frosty the Dogman
The pleasures of old age and good friends.
Frosty Face (from Lori)
Bubbie
Frankenbutt (after mast tumor removal)
Zipper/Zippy Butt
Like shadows, we’re not really here, but the memories always will be.