Day 1:
One thing you can do to make your iOS app better today 📱
1️⃣ Activate Voice Over
2️⃣ Now, turn on screen curtain (3 finger triple tap)
3️⃣ Try to navigate your app and look for places to improve VoiceOver navigation
🍻Day 2🍻
1️⃣ Hitting performance issues and not sure where to start?
2️⃣ Use os_signpost and .pointsOfInterest category to help benchmark your code
3️⃣ Then, use instruments to dial in on what the problems might be or where to start tuning your code
📱Day 3📱
1️⃣ Enable malloc stack logging for your scheme and run the Allocations template in Instruments
2️⃣ Open a few highly trafficked view controllers
and close them
3️⃣ Search for them in Instruments and make
sure they've deallocated
🕺🏻Day 4🕺🏻
1️⃣Check if your app name sounds correct when Siri or the system speaks it
2️⃣If it isn't, set a value for CFBundleSpokenName in your info.plist
3️⃣For example, Super App 14 would be "SuperApp Fourteen" instead of "SuperApp One Four"
🍻Day 5🍻
1️⃣ Edit your scheme and open Launch Arguments
2️⃣ Add "NSDoubleLocalizedString YES"
3️⃣ Run your app and see if your interface handles the longer text, which will now be doubled up (i.e. "Hi There" becomes "Hi There Hi There")
📱Day 6📱
1️⃣ Turn on Voice Control
2️⃣ Ensure you've set sensible values for the accessibilityUserlnputLables array
3️⃣ If you don't, values for Voice Over can aggregate too much content to be useful - especially in places like table view cells
👋 Day 7 👋
1️⃣ Use device info to help battery life
2️⃣ Check Processlnfo.processlnfo.is
LowPowerModeEnabled
3️⃣ If you've got blur views, high impact processes, location fetches or similar
tasks - toggle them off or on depending on the flag
📸Day 8📸
1️⃣If you've got images outside of the asset catalog, used in bundles or network calls - resize them to fit the images view's size
2️⃣Use ImageIO's thumbnails to make the decode occur only at the size you need
3️⃣ Now your data buffers will be much smaller during decoding
🌍Day 9🌍
1️⃣Edit your scheme and go to Launch Arguments
2️⃣Add in any language to test localizations for by adding
"-AppleLanguages (#country code here)"
3️⃣Any ISO 639 code will work. This way you don't need to go to sim settings or make multiple schemes just to test a language
🪑Day 10🪑
1️⃣If you've got a custom UI for notifications, deep link to it
2️⃣ Link it from iOS' Settings app via providesAppNotificationSettings on a UNNotificationSettings instance
3️⃣ Open it via the userNotification(_ center:
openSettingsFor:) delegate method.
📏 Day 11 📏
1️⃣ On text controls, set adjustsFontForContentSizeCategory and use a text style to support Dynamic Type
2️⃣ If you use custom fonts, use UIFontMetrics
3️⃣ If you don’t do either, you’ll need to leverage trait collection changes or content size notifications
📈Day 12📈
1️⃣Take a .memgraph file and open it in Terminal using $vmmap -summary
2️⃣Then use regex to look for any frameworks or objects that might be gobbling up memory
3️⃣Ex: vmmap -summary | grep 'Image l/O' to view clean, dirty and compressed memory for media
🔊Day 13🔊
1️⃣ Users may not want videos playing automatically due to motion sensitivity, distraction, motor disability, etc
2️⃣ We can use UIAccessibility to see whether or not folks prefer auto playback
3️⃣ Look at .isVideoAutoPlayEnabled to disable auto playback if it's false
📏Day 14📏
1⃣ Support RTL languages efficiently by using the correct constraints
2⃣ Opt for leading and trailing NSLayoutAnchor instances, not right and left ones - these adapt to RTL languages
3⃣ If it's long form text, consider using the readableContentGuide's version of these
📦Day 15📦
1⃣ Audit draggable items in your app to see which should make their own window
2⃣ For example, dragging a recipe in a meal planning app should support creating its own window on iPadOS
3⃣ Here, register the user activity with NSItemProviderRepresentationVisibility.all
🚀Day 16🚀
1⃣ In your scene delegate, implement stateRestorationActivity(for:)
2⃣ Populate the user info dictionary with state data
3⃣ Now, you can properly restore your app's state to create a seamless "relaunch" experience
🌎 Day 17 🌎
1️⃣ For any user activity you’ve got, consider if it should be publicly indexed via Apple’s servers
2️⃣ Set isEligibleForPublicIndexing to true if so
3️⃣ Then use either contentAttributeSet, keywords or webpageURL properties for indexing data
📸Day 18📸
1️⃣Leverage the ImageI0 framework for fine grain gif playback that's also very performant
2️⃣Use `CGAnimatelmageAtURLWithBlock()' to start playback
3️⃣You can tweak start frames, delays and more.
🤖 Day 19 🤖
1️⃣ In Xcode, choose Product -> Analyze
2️⃣ The static analyzer will run and usually picks up several issues you may
have overlooked
3️⃣ It'll catch dead stores, memory management issues and more. I use this
about once a month.
🐁 Day 20 🐁
1️⃣ Audit your app’s iPadOS pointer interaction support
2️⃣ For elements that could use a custom pointer shape, consider using a UIBezierPath to construct a custom one
3️⃣ Also decide whether a hover, lift or highlight interaction is right for each element
✏️ Day 21 ✏️
1️⃣ If you support Apple Pencil (gen 2), listen for double tap actions
2️⃣ Assign a delegate for 'UlPencillnteraction' and implement
'pencillnteractionDidTap'
3️⃣ From there, perform a system action provided or do something custom
👉 Day 22 👈
1️⃣ AddUlApplicationSupportsIndirectInputEvents to your info.plist
2️⃣ Now indirect event touch types are of type 'indirectPointer', handled by pinch or rotation gestures
3️⃣ This is good to get ahead of, because in a future
release this behavior will be the default
🔥 Day 23 🔥
1️⃣ For accessible drag and drop support, use UlAccessibilityDragging
2️⃣ This allows you to do two things: provide info on what drags+drops are possible
3️⃣ Think of a table view cell that supports swipe to delete *and* drag and
drop. You need to disambiguate the two.
🎛 Day 24 🎛
1️⃣ If you’ve got an animation where switch control scanning should pause, post .pauseAssisstiveTechnology
2️⃣ Pass AssistiveTechnologyIdentifier.notificationSwitchControl
3️⃣ Then, when the animation is done, post .resumeAssisstiveTechnolgy using the same ID
🐭 Day 25 🐭
1️⃣ Use pointer interactions in the right places. Use highlight for smaller elements with transparent backgrounds.
2️⃣ Lift is for smaller elements with an opaque background
3️⃣ Hover is for larger elements which could benefit from a custom tint, scale or shadow.
📑 Day 26 📑
1️⃣ Be intentional about where you store local data.
2️⃣ Use iCloud Drive + docs for things that should show in Files.app
3️⃣ App support/cache/temp are ideal for private use cases specific to your app
🚀 Day 27 🚀
1️⃣ Save docs in the right format & without a stringy API
2️⃣ Leverage the UTI framework to give you a strongly typed file extension - .appendingPathComponent("index"
conformingTo:.html)
3️⃣ This allows you to append almost any file type, returning the right extension
🖨 Day 28 🖨
1️⃣ Leverage AirPrint and the PDF framework to make a pretty, printable doc easily from your app’s content
2️⃣ Use UlGraphicsPDFRendererFo
rmat to set things like an author, title, etc.
3️⃣ Then UlGraphicsPDFRenderer will give you a context to create the .pdf itself
📝 Day 29 📝
1️⃣ Getting burned by a rouge notification and not
sure where to start?
2️⃣Dump all of them! Then sift through to look for anything unexpected.
3️⃣As simple as: NotificationCenter.default. addObserver(forName: nil, object: nil, queue: nil) { note in
print(note)
}
📱Day 30📱
1️⃣Need to debug widgets, but only at a certain size?
2️⃣Open the widget's scheme, and add an environment variable for XCWidgetFamily
3️⃣Use small, medium, large or extraLarge and the widget will boot straight to that size.
⌨️Day 31⌨️
1️⃣ Using Console.app to debug data, but finding that the format is a little messy?
2️⃣ Using string interpolation, you can do things like nudge text left or right easily.
3️⃣ Ex: logger.log("Formatted cols: \(testData, align: .left(columns: 2))")
🔗 Day 32 🔗
1️⃣Do you show content from URLs in your app?
Consider using LPLinkView to display them.
2️⃣It works with both remote URLs *and* local URLs
3️⃣However, be sure to not fetch metadata from the
same instance twice, or it'll crash.
💻 Day 33 💻
1️⃣ When you've got multiple scenes active, be sure to route to the most sensible one when a notification comes arrives for deep linking
2️⃣ Use the targetContentldentifier on UNNotificationContent
3️⃣ Leverage NSPredicate to match up to the right scene to handle it
🤳 Day 34 🤳
1️⃣ Displaying Live Photos is a unique angle to iOS, but many apps don't support their format when it's actually quite easy.
2️⃣ Instead of UIImageView, use PHLivePhotoView.
3️⃣ Denote a Live Photo in a list using a badge via PHLivePhotoBadgeOptions
🤖 Day 35 🤖
1️⃣ Do you use lists as a parameter in your Siri Shortcut or Widgets? Consider adopting INObjectCollection
2️⃣ Use subtitleString for more info on each item
3️⃣ Plus, it allows of for thumbnail images using displayImage. Both properties are iOS 14+
🗺Day 36🗺
1️⃣ Want to use location data in widgets? Add "NSWidgetWantsLocation" to your widget's info.plist
2️⃣ Also, add the regular permission strings in your
app's info.plist
3️⃣ It's a good idea to use separate extensions if one uses location permissions, and the others don't
🎥 Day 37 🎥
1️⃣ Modern device cameras can use the new Apple ProRAW format
2️⃣You can support it in your app too by setting isAppleProRAWEnabled on AVCapturePhotoOutput
3️⃣ Be sure to check that the session and device
support it first
🔎 Day 38 🔎
1️⃣ Do you have search in
your app? Leverage UlSearchToken to power up the UX
2️⃣ This way, users can easily group search terms (ie tokens), add more or
remove them
3️⃣ Look at insertToken( :at:) on UlSearchTextField
🖌 Day 39 🖌
1️⃣Creating a custom SwiftUI view centered around
graphics which is comprised of several other views?
2️⃣ You can use Metal to boost performance
3️⃣ Considering flattening it into a single image rendered offscreen with the .drawingGroup() modifier
🤌 Day 40 🤌
1️⃣ Does your app have any fun interactive UX flows around dragging or
simulated gravity?
2️⃣ Pair them with a custom haptics pattern to liven it up and take the interaction to the next level
3️⃣ Use CHHapticPattern to match it up with your UX interactions and gestures
🔍 Day 41 🔍
1️⃣ NSUserActivity vs CoreSpotlight to index data - which should you use?
2️⃣ CSSearchablelndex can be used *before* a user navigates to data but has less functionality
3️⃣ NSUserActivity can do more but typically requires the user to act on that data before indexing
📷 Day 42 📷
1️⃣ If you've got an asset that is a Live Photo, be sure to hint at it
2️⃣ Use Ullmage’s class function, livePhotoBadgelmage(options:) to get a system rendered icon to display over it
3️⃣ Use liveOff for assets that are a Live Photo, and overContent for any in playback
📍Day 43📍
1️⃣ SF Symbols are a great option for displaying iconography, but sometimes their default weights don't represent what you want
2️⃣ Use SymbolConfiguration class to tweak size, weight and more
3️⃣ i.e., if a user prefers bold fonts, use a backing font config that's bold
📱Day 44📱
1️⃣You can use SiriKit to handle intents in-app.
2️⃣Use it when you need something that's
a bit more memory hungry (i.e. processing a photo)
3️⃣If you go this route, ensure your app launch is quick, since it'll occur in the background when handling the intent.
⬆️Day 45⬆️
1️⃣LPLinkView is the way to go showing rich links
2️⃣But, if you share it via the share sheet - don't refetch data if you have it.
3️⃣Just pass the existing metadata in 'activityViewControllerLinkMetadata() -> LPLinkMetadata' and it'll be a near instant process.
🔒Day 46🔒
1️⃣Got an intents extension that has sensitive user data?
2️⃣Require the device to be unlocked before it can executed.
3️⃣Select the intent, and in the general pane choose "Restricted While Locked" as the auth method.
📱Day 47📱
1️⃣Got something custom to share using the share
sheet?
2️⃣Subclass `UlActivityItem` with an image, title, type and more. These are required.
3️⃣Fire off your logic with either another override, perform()', or vend a controller to do the rest via
activityViewController.
🖖Day 48🖖
1️⃣ Need to tweak split view controller's display mode button? Check out UlSplitViewControllerDisplayModeButtonVisibility, added in 14.5
2️⃣ Use always, auto or never
3️⃣ Remember, the gesture to toggle the master view controller is also configurable
• • •
Missing some Tweet in this thread? You can try to
force a refresh
Many users invert iOS' colors to help with light & color sensitivities. But, ensure you aren't degrading rich media in your app, which users likely expect to remain at their true fidelity.
For images & media, you'll likely want them to forgo this setting.
💡Tip 11 Continued 💡
This requires almost zero effort to support - simply set accessibilityIgnoresInvertColors to false (which also will do the same thing for subviews of that view):
💡Tip 12 💡
Since iOS 13, users can enable Voice Control which allows one to navigate iOS by calling out prompts on screen.
By default, iOS vends your accessibilityLabel and in some cases, this might be too verbose. Instead, leverage the accessibilityUserInputLabel array.
I’ll be extrapolating out things I’ve learned, read from the HIG and have seen that make an iOS app look and feel good.
Tip 1) Make a table view row animate its selection state as it’s popping and pushing on the nav Stack.
Tip 1 Continued:
UITableViewControllers get this behavior for free. If you roll your own table view you’ll need to do it yourself. You can do so by grabbing the controller’s transition coordinator as seen in this (gasp!) Objective-C code here called in viewWillAppear 💫
💡Tip 2 💡
If you've got text whose primary function is long-form reading, make it easy to parse it by using the view's readableContentGuide.
For the past 2+ years I’ve been building my side project using #CloudKit and I’d love to share what I’ve learned.
There isn’t a ton of info out there on it, and I’ve got some tips that I’m not sure I’ll get around to blogging about so I’ll add them here in a thread 👇
First off - subscriptions. There are three main types:
CKDatabaseSubscription
CKRecordZoneSubscription
CKQuerySubscription
Unless you’re *only* using the default zone, you should start with CKDatabaseSubscription.
The reason is it vends custom zone changes and more importantly changes from a shared database. RecordZoneSub can’t do this, neither can a query sub.
If you know you’re for sure not supporting sharing, you can get away with a recordZoneSub *mostly*.
A fun bit of #macOS development history I stumbled upon today, Apple Tech Note 2034.
Essentially it included a bunch of guidelines and tips on macOS development but contained such inflammatory assertions that Apple straight up *pulled* it down (2033/5 are still there)!
Among some of the the things that didn't sit very well with devs in the note:
- Positing Objective-C as being a more portable cross-platform language than C++
- Promoting hard-coded pathname APIs for accessing files over traditional Mac APIs
Reading over it, quite a bit of the punch is lost on me since I started in this industry in a professional sense in 2012. But the same conversations happen today - Objective-C vs Swift being a similar parallel in that it garners hot opinions, fast.