[{"content":"With just a few weeks to go before WWDC 2026, here\u0026rsquo;s my wishlist for iPadOS in 2026!\nOS-level tabs This feature massively benefits screens with less real-estate to work with. It seems especially suited to iPad.\nIt exists as a macOS feature, but I\u0026rsquo;ve pulled this out of the grab-bag because I\u0026rsquo;d like to see Apple go further than the current macOS implementation, by allowing tabs from multiple apps in the same window. This would allow windows to be used like mini-workspaces, with a tabbed multitasking system.\nCompilerKit If arbitrary code execution is dangerous, allow it the right way - with a bunch of scary warnings, hoops to jump through, and a use-case specific Apple-vetted app capability. Throw up a scary prompt on downloading a project, and require going to Setting and entering a user-password to allow it to run, like with profiles.\nApple can\u0026rsquo;t keep innovating their hardware and building new OSs if they\u0026rsquo;re always going to be tied to macOS. When AR glasses become commonplace with natural language voice control and neural implants, will we still have to buy a laptop with hardwired keyboard and trackpad to build apps?\nPinned tab \u0026lsquo;homepages\u0026rsquo; in Safari When a tab is pinned, store the tab\u0026rsquo;s current full address permanently, and allow quickly reverting to it following navigations to different pages on the domain.\nThat\u0026rsquo;s all Arc\u0026rsquo;s fancy tab-bookmarks are, in essense, and with tab groups being makeshift folders, and Profiles being a feature too, this alone is the final feature which would have me switch for good.\nWindow Manager API What complaint do macOS and iPadOS have in common? No-one loves the stock windowing system, whatever it might be at any given time.\nOne of my more out-there ideas - I\u0026rsquo;d love to see an OS gain a privacy-first API for building window managers. This would allow options Apple may never choose to built themselves, like classic multitasking, automatic tiling window managers, and scrolling window managers to become possible.\nHomescreen inline folders \u0026amp; density I\u0026rsquo;d love to see more apps on the iPadOS homescreen, or configurable density.\nSomething which would help this be practical is inline app folders - visual groupings of user-selected apps, but with contents accessible with a tap - kind of like how the Siri Suggestions widget works currently.\nmacOS grab-bag Xcode for iPad I\u0026rsquo;ve put many words into this, but it bears mentioning - the app development functionality in Swift Playground is clearly on life-support and dying a slow death. The bugs just keep mounting version to version, and the app development space has moved on from the original Swift Playgrounds 4 featureset.\nWith pro apps having been available on iPad for a while, Xcode continues to be a glaring omission.\nKey asks:\nRegular updates Git built-in Local LLM assistant support Local package/module support Widget and test target support Customisable menubar keyboard shortcuts Another macOS feature, though a little-known one - macOS allows any menu bar item, app or system level, to be assigned a custom keyboard shortcut via System Settings. As a feature along the lines of customisable toolbars, I\u0026rsquo;d like to see this come to iPad, so even niche functionality can be readily available if it fits my workflow. I wish for this every time I reach for the window corner-tiling options in the menu bar.\nNew Search Experience Another straight copy from macOS. I\u0026rsquo;m particularly keen for the parameterised Shortcuts functionality as it\u0026rsquo;ll make a common task for me - running Shortcuts from Search - that much more seamless.\nUniversal Control between iPads This is what I thought Universal Control would be, before it wasn\u0026rsquo;t.\nDisplay folders as icons in Dock If you add multiple folders containing folders to your dock, you know the pain of being entirely unable to differentiate which folder is which.\nGatekeeper for iPad - 3rd party app distribution Apple is kidding themselves if they think they can keep a platform more secure through human vetting, and in many regions the ship is sailing. Governments of the world will force their hand on the issue if they don\u0026rsquo;t figure out a new monetisation strategy soon. Give us Gatekeeper, and Apple has a better chance of preserving the ability to pull the worst offending apps and block/vet dev accounts.\nDelivering on the rumours Some rumours which may, or may not, meet our expectations!\nThe death of UIRequiresFullscreen Worth a mention, as I\u0026rsquo;m sure Apple announced this 10 years ago but copped out.\nGames companies will be the biggest holdouts, but iPadOS 26 can enforce minimum window sizes, so there really is no excuse not to follow the macOS model at this point, and just require 1280 by 1080 points as minimum for the game\u0026rsquo;s window.\niPads come in all sizes now, with differing aspect ratios, and foldable phones seem inevitable. External display support is here. I really hope Apple deliver on this, and don\u0026rsquo;t let devs who don\u0026rsquo;t understand the system continue to hold out.\nNew Siri Rumoured, and I\u0026rsquo;m very excited for it if it can hook together shortcuts actions like MCP tools, as the rumours suggest. I trust Apple to build a safe permissions model around it, and the idea of Siri knowing my calendar, reminders, and notes is a game-changer for accessing knowledge stored on my phone (or watch!) when outside.\n","permalink":"https://mutatingfunc.github.io/blog/2026-05-23-ipados-2026-wishlist/","summary":"With just a few weeks to go before WWDC 2026, here\u0026rsquo;s my wishlist for iPadOS in 2026!\nOS-level tabs This feature massively benefits screens with less real-estate to work with. It seems especially suited to iPad.\nIt exists as a macOS feature, but I\u0026rsquo;ve pulled this out of the grab-bag because I\u0026rsquo;d like to see Apple go further than the current macOS implementation, by allowing tabs from multiple apps in the same window.","title":"iPadOS 2026 Wishlist"},{"content":"If you’ve ever thought Apple isn’t opinionated on app architecture, look again. App Intents has a type called AppDependencyManager built-in, which promotes injection of dependencies, and they even recommend using it in the main app target, setting it up in your App’s init. Buttons and Toggle can take an AppIntent. It’s almost like Apple built their own optional alternative to PointFree’s TCA.\nhttps://developer.apple.com/videos/play/wwdc2025/244/\nhttps://developer.apple.com/videos/play/wwdc2025/275/\nhttps://developer.apple.com/videos/play/wwdc2025/260/\nhttps://developer.apple.com/videos/play/wwdc2025/281/\n","permalink":"https://mutatingfunc.github.io/blog/2026-03-24-app-intents/","summary":"If you’ve ever thought Apple isn’t opinionated on app architecture, look again. App Intents has a type called AppDependencyManager built-in, which promotes injection of dependencies, and they even recommend using it in the main app target, setting it up in your App’s init. Buttons and Toggle can take an AppIntent. It’s almost like Apple built their own optional alternative to PointFree’s TCA.\nhttps://developer.apple.com/videos/play/wwdc2025/244/\nhttps://developer.apple.com/videos/play/wwdc2025/275/\nhttps://developer.apple.com/videos/play/wwdc2025/260/\nhttps://developer.apple.com/videos/play/wwdc2025/281/","title":"App Intents"},{"content":"The MacBook Neo just came out. It looks great. Fun colours, low price. It’s objectively better in every way than my M1 MacBook Air.\nI could use it for Xcode, but I’m going to have a nicer experience for everything else on an iPad with Magic Keyboard. The non-touch, non-convertible laptop is a dated form factor - it will always feel crippled in basic HCI terms vs an iPad. I feel measurably more comfortable having the ability to between input methods as I use my computer. I don\u0026rsquo;t want one of these 2010s-era laptops in 2026.\nI still can’t justify buying a computer just for Xcode, and that\u0026rsquo;s immensely frustrating. I\u0026rsquo;m a coder! I love building tools. So I find myself considering other options more and more:\nMaybe a Surface or Linux tablet would let me build apps and have a nice form factor. 🤔 Compose Multiplatform could let me build apps in Android desktop mode via the Linux subsystem and have them run on iOS too. 🤔 Why would I buy a sucky laptop for coding, when there’s so many innovative options to try out there?\nApple should see this inability to escape macOS and the laptop form factor as a serious problem.\nThey launched a whole new platform recently. Vision Pro. An AR workstation computer! A Mac-like approach, floating windows.\nWhat did people do with it? They used it as an expensive monitor. For their Mac laptops.\nThis is a massive waste of potential, and a massive amount of money left on the table.\nBut to the contrary, I feel like so many \u0026rsquo;tech enthusiasts\u0026rsquo; are buying into the idea that Apple doesn\u0026rsquo;t need to innovate - the laptop form factor is the holy grail for \u0026lsquo;getting work done\u0026rsquo;, ignoring that many professionals\u0026rsquo; needs are simply not met by old-style laptops, and not all professionals sit at a desk. Shudder to think we could try making incremental improvements to the status quo.\nPeople say just \u0026lsquo;get a Mac and an iPad\u0026rsquo; or even \u0026lsquo;just get a Mac\u0026rsquo; as if obviously you should buy the same hardware a second time, or accept compromises.\nAlmost no-one in the UK outside of tech uses iPhones, or Macs. I wonder why. Are they well-made? Yes. Are they powerful? Yes. Are they making innovative new products that redefine how people use computers? Not by a long shot.\nAre they saving me money?\nShould normal people care about, and empty their wallet for, powerful and well-made but totally boring commodity products?\nSomewhere along the way, we\u0026rsquo;ve lost \u0026lsquo;Apple\u0026rsquo;. Now we have a new \u0026lsquo;Apple Computer, Inc\u0026rsquo; - big and corporate, and stuck in the past.\n","permalink":"https://mutatingfunc.github.io/blog/2026-03-07-apple-computer-inc-in-2026/","summary":"The MacBook Neo just came out. It looks great. Fun colours, low price. It’s objectively better in every way than my M1 MacBook Air.\nI could use it for Xcode, but I’m going to have a nicer experience for everything else on an iPad with Magic Keyboard. The non-touch, non-convertible laptop is a dated form factor - it will always feel crippled in basic HCI terms vs an iPad. I feel measurably more comfortable having the ability to between input methods as I use my computer.","title":"Apple Computer, Inc in 2026"},{"content":"I’m an LLM sceptic. LLMs controlling the computer is a security nightmare, misinformation is to be avoided.\nMost modern agent clients build in some level of security, turning repetitive input tasks into a series of outputs \u0026amp; permissions dialogs. Permission dialogs suck, but output review is still faster than extended inputs.\nThere’s a workflow here:\nGive a request. The LLM builds a small code-based tool to carry it out. Human reviews, tweaks, approves the tool. Automation everywhere! ⚙️ Meanwhile Apple are doing amazingly on the chips front. I’ve been playing with some local LLMs for tool use, and they’re reaching a threshold where they can use and even build tools competently enough to be useful, entirely offline.\nIf these can run locally and build reliable automations in the course of normal computer use, that’s a gamechanger for how we interact with computers in 10 years time when this hardware is commonplace.\nEven us automation nerds with 300+ shortcuts have a limit!\nSo, given:\nLocal LLM with hardware to power it. Manual confirmation for any IO tasks by the LLM. Trustworthy code-based tools. Direct tool output in the UI, no LLM filter. I think that’s my line. The line where I can actually get really really excited about this tech.\n","permalink":"https://mutatingfunc.github.io/blog/2026-03-07-a-sceptical-look-at-llms/","summary":"I’m an LLM sceptic. LLMs controlling the computer is a security nightmare, misinformation is to be avoided.\nMost modern agent clients build in some level of security, turning repetitive input tasks into a series of outputs \u0026amp; permissions dialogs. Permission dialogs suck, but output review is still faster than extended inputs.\nThere’s a workflow here:\nGive a request. The LLM builds a small code-based tool to carry it out. Human reviews, tweaks, approves the tool.","title":"A sceptical look at LLMs \u0026 Agents"},{"content":"I just got myself a like-new Galaxy Fold for the cost of the new iPhone 16e (!), and I’m realising it’s what I always wanted from the iPad Mini - a premium device with a nice, 120Hz screen, which you can actually pocket and carry about as a phone too.\nI’ve been all-in Apple for 20 years or so, and have been getting a bit frustrated with them recently and wanting to try something new, if only to prove to myself it’s better than the alternative. I don’t anticipate being able to switch fully, I’m in too deep and love Apple’s stock apps too much for that. Apple just feels stagnant. Most laptops have touchscreens now, and many also work as tablets. Foldables are a refined market. I’ve tried the Vision Pro, but I’ve used VR headsets with passthrough before, so I was mostly blown away by the small size compared to older headsets. The price will come down, but Apple’s pitch of wearing a headset for 8 hours a hypothetical workday is really not that compelling a sell for me in the first place - for sure, it’s a powerful work environment, but it’s also an ergonomic nightmare.\nThe Fold, meanwhile, just feels so fun and personal, to have this little sketchpad that folds up! It’s a tablet that fits in your pocket! I’ve not felt this excited about a new computing device from Apple for years. It’s not trying to change the world, or reinvent the industry. It’s just trying making a pocket device that’s a little more human, and year by year figuring out the techniques to do so.\nWhile tablet apps aren’t quite as good on Android, I’ve been pleasantly surprised by the support built into stock apps from Samsung and Google. It’s also worth noting web-apps actually run native-level smooth, and you have a backdoor out of phone-size app letterboxing, so Android has it’s tablet perks too.\nBut Android also has an ugly side - apps aren’t even apologetic about trying to make you sign your life away in their consent pages, it’s kind of scary. Popular nerdy apps that would be someone’s passion project on iOS, will demand tangental permissions just to launch and then happily sell you out to their ad providers.\nIf Apple releases a fold-up iPad Mini / iPhone running proper iPad apps, it’ll be an instant buy for me. But I also see so many sharp corners coming, like apps needing a recompile to run without letterboxing, or no ability to use landscape on the outer screen / partially folded mode for propping up on a table… Could you imagine if it ships with Stage Manager! That’d make it the dream device for me, a true all-in-one.\nApple lately seems to be dead set on creating the best products in their category, that redefine industries. Vision Pro. Airpods Max. The best cameras ever in an iPhone. The best displays ever in an iPad. Would it kill them to just have some fun once in a while, and put something out using existing tech in new ways, just to see if it catches on?\n","permalink":"https://mutatingfunc.github.io/blog/2025-03-29-my-unfolding-experiment-with-foldables/","summary":"I just got myself a like-new Galaxy Fold for the cost of the new iPhone 16e (!), and I’m realising it’s what I always wanted from the iPad Mini - a premium device with a nice, 120Hz screen, which you can actually pocket and carry about as a phone too.\nI’ve been all-in Apple for 20 years or so, and have been getting a bit frustrated with them recently and wanting to try something new, if only to prove to myself it’s better than the alternative.","title":"My Unfolding Experiment With Foldables"},{"content":"Apple has released the long-awaited Swift Playgrounds 4.6 update, or as it\u0026rsquo;s now known, Swift Playground!\nThe release notes read as follows:\nThis update includes a new document browser to easily create a new playground or find a recent one, and provides bug fixes and improved stability.\nLet\u0026rsquo;s see what\u0026rsquo;s new!\niOS 18 Support! Despite the description, the update does include support for targeting SDKs up to iOS 18.1.\nInterface changes The update also includes the new document browser demoed in June last year which is a big win for organising projects! I just don\u0026rsquo;t understand why it\u0026rsquo;s taken this long.\nThe updated App Store screenshots also include a mysterious new toolbar item in the app editor, which looks like a set of books on a bookshelf. However, this button seems completely absent in the update itself!\nUpdate: Turns out this is a button that appears when you use a tutorial template! You learn something new every day!\n“Bug fixes” The release notes claim this update includes bug fixes, but the most severe bugs like jumping to types directly using Open Quickly, or the typing into text fields in the previews area, are still broken, so I\u0026rsquo;m not sure what they claim to be fixing here.\nSwift 6? New projects still make use of Swift tools version 5.9 by default. The update uses the Swift 6 compiler, in Swift 5 language mode with partial concurrency checking.\nThe following code compiles in the new IDE:\nprotocol NonCopyableProtocol: ~Copyable { } func typedThrows() throws(NSError) { } There is no in-app option to specify language mode in the editor, but it is technically possible to edit the Package.swift manually to specify one. I\u0026rsquo;ve heard that Swift 6 mode will break full app previews for your project though, so use this with caution.\nEvery learn-to-code template is still listed as “Swift 5.8 Edition”, but at least they\u0026rsquo;ve updated the “Playground” and “Book” templates listed right at the end though, which are listed as “Swift 5.9 Edition”.\nIt\u0026rsquo;s unclear what took this update so long to arrive, but I\u0026rsquo;m glad it\u0026rsquo;s finally here! It brings much-needed parity with Xcode, which will allow me to start making my projects Swift Playground-compatible.\n","permalink":"https://mutatingfunc.github.io/blog/2025-01-31-swift-playgrounds-4-6/","summary":"Apple has released the long-awaited Swift Playgrounds 4.6 update, or as it\u0026rsquo;s now known, Swift Playground!\nThe release notes read as follows:\nThis update includes a new document browser to easily create a new playground or find a recent one, and provides bug fixes and improved stability.\nLet\u0026rsquo;s see what\u0026rsquo;s new!\niOS 18 Support! Despite the description, the update does include support for targeting SDKs up to iOS 18.1.\nInterface changes The update also includes the new document browser demoed in June last year which is a big win for organising projects!","title":"Breaking down Swift Playgrounds 4.6"},{"content":"The story so far Last year, I developed and released Medlied from my iPad using Swift Playgrounds. I cut out the Mac from my workflow completely, enjoyed live on-device previews that update in seconds, and I even got the privilege of doing a talk on my experience using Swift Playgrounds at Leeds Mobile!\nUnfortunately, this setup still has some holes which I ran into with my next project.\nFirst, Swift Playgrounds support for iOS 18 has failed to materialise. With the Swift Student Challenge only four days away, Apple has left iPad users without a way to try out the iOS 18 SDKs. Consequently, SwiftPM projects edited in Xcode may no longer compile when reopened in Playgrounds, and due to Swift limitations, which have gone unaddressed on the basis that everyone using Swift uses Xcode, no amount of conditional compilation can actually work around the issue.\nSecond, for Shimmer Browser, I knew I wanted it to be free to use, with a full unlock option. As Swift Playgrounds does not offer the in-app purchase capability flag (or maybe it does, but I\u0026rsquo;d be guessing at undocumented SwiftPM syntax), I couldn\u0026rsquo;t continue building it purely in Swift Playgrounds.\nReluctantly, I\u0026rsquo;ve dug out my MacBook Air again and used it to power my iPad\u0026rsquo;s external display for Xcode. Having breakpoints available without quickly changing editor is handy, but I miss the power of the M4 on my iPad, and the ability to have live previews of the full running app in 1-2 seconds, rather than waiting for my M1 Air to built the app, then another 30 seconds for the debugger to connect and allow the app to run!\nI described a way to add local packages to a Swift Playgrounds app in my previous blog post. It\u0026rsquo;s possible to upload a package built in this way to Github, and import the library in Xcode. I would have used this to move everything back to a Swift Playgrounds library-app by now, with 3 (!) local packages for each of the app, widget, and shared code, but doing so now would also mean stripping out iOS 18 functionality, which I make use of in a few places to support iOS 18\u0026rsquo;s widget tinting feature properly. I\u0026rsquo;ll be relieved to move most of my editing back to iPad when the time comes!\nSo, I\u0026rsquo;ve put together a quick wishlist for what I want to see come to the iPad, either via the long-overdue Playgrounds update… Or the secret Xcode for iPad project that Apple will never mention until they release it unexpectedly, following in the footsteps of Final Cut and Logic.\niPad coding First aid iOS 18 support (duh) A way to take screenshots for the App Store, as you can\u0026rsquo;t upload without this. I don\u0026rsquo;t mind if the answer is Apple sending me a new iPad and iPhone each year. For professionals In-app purchases, subscriptions, and all the monetisation capabilities Xcode branding, and/or a commitment to actually updating whatever the leading iPad IDE is Bug fixes Build targets (including tests support) No seriously, ongoing regular bug fix updates Breakpoints Crash reports Or even just stack traces for crashes… Oh wait, this used to work, but it needs a bug fix! Localisation support Accessibility inspector Performance graphs, memory graphs, and instruments more widely Nice-to-haves Local app installation; nice-to-have because one can use Testflight as a workaround Built-in git integration Xcode Cloud Support for dependencies in private repos Direct Package.swift file editing Oh if you must Apple Intelligence See you in 2026! If I can check off even just the first half dozen points on this list this year, I\u0026rsquo;ll be happily iPad-only for all my personal projects, not just IAP-free ones!\n","permalink":"https://mutatingfunc.github.io/blog/2025-01-30-ipad-coding-wishlist-2025/","summary":"The story so far Last year, I developed and released Medlied from my iPad using Swift Playgrounds. I cut out the Mac from my workflow completely, enjoyed live on-device previews that update in seconds, and I even got the privilege of doing a talk on my experience using Swift Playgrounds at Leeds Mobile!\nUnfortunately, this setup still has some holes which I ran into with my next project.\nFirst, Swift Playgrounds support for iOS 18 has failed to materialise.","title":"My iPad coding wishlist for 2025"},{"content":" In the last year, I\u0026rsquo;ve gone all-in on building apps using an iPad. I\u0026rsquo;ve never met another Playgrounds developer, and a lot of the community are surprised this is even possible!\nThis post should clear up what is and isn\u0026rsquo;t possible when it comes to app development on iPad, and I hope to encourage others to give this a try too!\nI use my iPad hooked up to a keyboard, mouse, and monitor for most serious work, with both screens set to More Space. It\u0026rsquo;s very much comparable to how most people would use their Mac. For more on why my primary device is an iPad, see my post on iPadOS vs macOS.\nWhat is Swift Playgrounds? Swift Playgrounds is to Xcode, what Garageband is to Logic Pro. It\u0026rsquo;s a fully capable IDE, that\u0026rsquo;s easier to use, but doesn\u0026rsquo;t have all the features of its older sibling.\nSwift Playgrounds was, and still is, pitched as an educational tool by Apple. It can also work with \u0026ldquo;Playground Books\u0026rdquo;, an educational format where users can be guided through the basics of coding.\nSince Swift Playgrounds 4, Apple introduced a new app project format across Playgrounds and Xcode, based on Swift Package Manager. A Package.swift file defines the structure of the entire project, similar to how one would declare a Swift Package. Unlike Swift Packages though, the whole project is bundled into a .swiftpm bundle - a folder dressed up to look like a file.\nI fully expect Apple is working on Xcode for iPad, along the same lines as how they\u0026rsquo;ve brought Final Cut and Logic Pro. However, for now, Swift Playgrounds is what we\u0026rsquo;ve got to work with.\nGetting started To begin, create a new app project! Expand the project navigator sidebar, and you\u0026rsquo;re set. It\u0026rsquo;s that easy!\nFeatures The editor Swift Playgrounds supports many of the features you\u0026rsquo;d expect from Xcode. You can add files, folders, supporting files, and package dependencies. .xcasset files are supported, but managed automatically and hidden from the UI. Info.plist files are also supported, but require a bit of setup (I\u0026rsquo;ll get on to this later). App tint colours and icons can be customised manually.\nKeyboard shortcuts match Xcode where equivalent functionality is available, such as opening the documentation window or the Open Quickly search popup. The toolbar is customisable, which I hope to see come to Xcode!\nYou can also edit app metadata like icon and the app-wide default tint colour, by selecting the Package name at the top of the project navigator sidebar.\nPreviews App previews display as they do in Xcode, but Swift Playgrounds actually has two extra superpowers. First, Swift Playgrounds is much more proactive about rebuilding previews as changes are made, very rarely requiring the hitting of a reload button like in Xcode. Second, your entire app is recompiled live, and displayed on the side as a preview!\nIt is still possible to run the app in a separate window by hitting the run button in the toolbar. However, to test multi-window apps you\u0026rsquo;ll have to run a build through Testflight.\nSwift As the name suggests, only Swift is supported in Swift Playgrounds. If you\u0026rsquo;re willing to use a new project format, it\u0026rsquo;s likely you\u0026rsquo;re starting a new project, and wouldn\u0026rsquo;t want to use Objective-C anyway.\nUIKit There seems to be a pre-conception that Swift Playgrounds only supports SwiftUI. UIKit is fully supported too!\nAny framework you might import to an Xcode project is available.\nGit versioning It is possible to use Git with Swift Playgrounds! My recommendation of Git client is Working Copy, as it lets you form a git repo in-place in the filesystem. I\u0026rsquo;d prefer a more traditional UI with a focus on the branch tree, but while options are available, they tend to prefer to pull the whole project into their own sandbox.\nTo set up a repo for your SwiftPM project, select the Add Repo button in Working Copy, \u0026ldquo;Link external repository\u0026rdquo;, and select \u0026ldquo;Document Package\u0026rdquo;.\nAs a bonus, Working Copy allows editing the files within the SwiftPM bundle directly, which unlocks a few extra capabilities…\nInfo.plist It\u0026rsquo;s possible to add an Info.plist to a SwiftPM project.\nAdd an Info.plist file into the SwiftPM bundle folder. Add the following to the bottom of the \u0026ldquo;.iOSApplication\u0026rdquo; product definition, adding a comma separator on the line above: additionalInfoPlistContentFilePath: \u0026#34;Info.plist\u0026#34; While the Package.swift file warns against manual editing, this is not an issue in practice for any version of Swift Playgrounds released to date.\nBuilding packages It\u0026rsquo;s also possible to build packages using Swift Playgrounds! This again requires some manual editing of the Package.swift.\nTo build a package using Swift Playgrounds, add a library target to your Package.swift, and set up the main app target to depend on it.\nlet package = Package( name: \u0026#34;MyPackage\u0026#34;, platforms: [ .macOS(\u0026#34;14.0\u0026#34;), .iOS(\u0026#34;17.0\u0026#34;), .tvOS(\u0026#34;17.0\u0026#34;), .watchOS(\u0026#34;10.0\u0026#34;), .macCatalyst(\u0026#34;17.0\u0026#34;) ], products: [ .library( name: \u0026#34;MyPackage\u0026#34;, targets: [\u0026#34;MyPackage\u0026#34;] ), .iOSApplication( name: \u0026#34;MyPackage_TestApp\u0026#34;, targets: [\u0026#34;MyPackage_TestApp\u0026#34;], teamIdentifier: \u0026#34;\u0026#34;, displayVersion: \u0026#34;1.0\u0026#34;, bundleVersion: \u0026#34;1\u0026#34;, appIcon: .placeholder(icon: .leaf), accentColor: .presetColor(.red), supportedDeviceFamilies: [ .pad, .phone ], supportedInterfaceOrientations: [ .portrait, .landscapeRight, .landscapeLeft, .portraitUpsideDown(.when(deviceFamilies: [.pad])) ] ) ], targets: [ .target( name: \u0026#34;MyPackage\u0026#34;, path: \u0026#34;MyPackage\u0026#34; ), .executableTarget( name: \u0026#34;MyPackage_TestApp\u0026#34;, dependencies: [ \u0026#34;MyPackage\u0026#34; ], path: \u0026#34;MyPackage_TestApp\u0026#34; ) ] ) This configuration asserts that files for \u0026ldquo;MyPackage\u0026rdquo; will exist in a \u0026ldquo;MyPackage\u0026rdquo; folder (as defined by \u0026ldquo;path\u0026rdquo;), and the app files will exist in \u0026ldquo;MyPackage_TestApp\u0026rdquo;. You will need to create each, move your SwiftUI files to the app folder, and ensure the new library folder contains at least one valid Swift file, to get the project building.\nWhile we can\u0026rsquo;t get rid of the main app target entirely, here we\u0026rsquo;re repurposing it as a mini-app for testing the key functionality of your library. If your library doesn\u0026rsquo;t have much UI, you could even have the app display unit test results live in the previews area!\nOnce you\u0026rsquo;re happy with your library, using your Git client of choice to push it up to Github, and add the Github repo as a dependency to your main app! If you prefer, you can even add the library target directly to your main app, to edit both in a single project.\nUpload to App Store Connect Once you\u0026rsquo;re happy with your app, Swift Playgrounds contains a button to upload the build directly to App Store Connect. If the app does not yet exist on App Store Connect, Playgrounds will allow you to create it as part of the upload. Make sure to set your app\u0026rsquo;s version number before upload!\nWhere in Xcode, you\u0026rsquo;d have to increment a build number for each build, Swift Playgrounds handles this for you.\nThe drawbacks Swift Playgrounds does have a few limitations.\nBreakpoints The one I run into most often is the lack of breakpoints. While sometimes a crash will provide a stack trace, I\u0026rsquo;ve been finding this functionality increasingly flakey. However, for the most part print statements get me by.\nTargets Another limitation is the lack of support for targets and schemes, which are critical for supporting (native) unit tests and UI tests. This rules out using Playgrounds for certain extension types, where the best you\u0026rsquo;ll get is moving most of the extension\u0026rsquo;s code into a library.\nHowever, for unit tests with properly mocked dependencies, previews can be converted into a means to display unit test results, which has the added benefit of appearing contextually as you edit the file and displaying results live as you type. I\u0026rsquo;ve created a few small packages to support this which can be found on my Github, though I\u0026rsquo;ve not yet used them in earnest in a project.\nSimulators Swift Playgrounds does not support simulators. This is perhaps the most critical omission, but not for the reason you might think. You can test at most device sizes simply by running the app, and resizing the window using Stage Manager. This makes iPad the ideal environment for developing well-behaved, resizable apps.\nHowever, it\u0026rsquo;s when it comes to a release that the lack of simulators reveals itself to be an issue. Good luck resizing a window to the pixel-perfect dimensions of a specific device for taking App Store screenshots! I\u0026rsquo;ve also created a small package to work around this, which can take screenshots with the environment set to simulate specific device dimensions, safe area insets, and display scales, but unfortunately SwiftUI modals completely ignore the environment and do their own thing.\nUpdate schedule The update schedule for Playgrounds can only be described as \u0026ldquo;Whenever the team feels like it\u0026rdquo;. You typically have a 1-2 month wait from the new major OS releases, to actually being able to make use of the SDKs in Playgrounds.\nThere is one exception to this. In 2023, Apple shipped a TestFlight build at WWDC with support for the new SDKs, which showed alongside the Xcode beta download on developer.apple.com. But that\u0026rsquo;s all we got. In September, a week before the new OS versions shipped, this single TestFlight build expired at 3 months old, leaving us without any way to even edit the projects we\u0026rsquo;d updated to the new releases.\nConclusion Swift Playgrounds is a fully capable replacement for Xcode, and it\u0026rsquo;s opened up coding as an option again on my primary device. Xcode has been the only reason I ever pick up my Mac for the last few years, and I\u0026rsquo;m keen to cut out the need for it - if you\u0026rsquo;ve ever installed Windows on your Mac for gaming, you know the feeling. The one, single roadblock to going all-in on Playgrounds will be taking screenshots for the App Store, of all things, but I\u0026rsquo;m sure this is a surmountable problem.\n","permalink":"https://mutatingfunc.github.io/blog/2024-10-12-app-development-on-ipad/","summary":"In the last year, I\u0026rsquo;ve gone all-in on building apps using an iPad. I\u0026rsquo;ve never met another Playgrounds developer, and a lot of the community are surprised this is even possible!\nThis post should clear up what is and isn\u0026rsquo;t possible when it comes to app development on iPad, and I hope to encourage others to give this a try too!\nI use my iPad hooked up to a keyboard, mouse, and monitor for most serious work, with both screens set to More Space.","title":"App development on iPad"},{"content":"Previews are a handy tool for getting a live look at all possible states of a view, and I often finding myself creating loads without thinking.\nYet I find people often have trouble getting previews to work nicely. This is my attempt to break down my approach for those people.\nIf you use TCA, which ties architecture very deeply to the view, my best advice is to look to their docs on the topic.\nThe standard approach The standard approach of using a view model, no protocols, looks a little like the following:\nimport SwiftUI // View-agnostic model struct WhoppingGreatAPIResponse: Decodable { var countFoos: Int // Some hard-to-construct additional data } actor APIHandler { func makeRequest() async {} } // View Model @Observable class ContentViewModel { var thingA: Int var thingB: String private var apiResponse: WhoppingGreatAPIResponse private var apiHandler: APIHandler init(thingA: Int, thingB: String, apiResponse: WhoppingGreatAPIResponse, apiHandler: APIHandler) { self.thingA = thingA self.thingB = thingB self.apiResponse = apiResponse self.apiHandler = apiHandler } var outputText: String { if apiResponse.countFoos \u0026lt; 0 { \u0026#34;Hello world! Your number is \\(thingA * 123) and your string is \\(thingB)\u0026#34; } else { \u0026#34;Negative foos shouldn\u0026#39;t happen\u0026#34; } } func submitRequest() async { await apiHandler.makeRequest() } } // View struct ContentView: View { @Bindable var viewModel: ContentViewModel var body: some View { VStack { Text(viewModel.outputText) Button(\u0026#34;Do thing\u0026#34;) { Task { await viewModel.submitRequest() } } } } } // Previews #Preview(\u0026#34;Success\u0026#34;) { ContentView(viewModel: ContentViewModel(thingA: 1, thingB: \u0026#34;2\u0026#34;, apiResponse: WhoppingGreatAPIResponse(countFoos: 123), apiHandler: APIHandler())) } #Preview(\u0026#34;Failure\u0026#34;) { ContentView(viewModel: ContentViewModel(thingA: 1, thingB: \u0026#34;2\u0026#34;, apiResponse: WhoppingGreatAPIResponse(countFoos: -123), apiHandler: APIHandler())) } // Entry point for app, single page showcase app, in-app showcase listing, etc struct DataView: View { @Bindable var viewModel: ContentViewModel var body: some View { ContentView(viewModel: viewModel) } } With this approach, previews are clearly no good. The view model (be it traditional, or an alternative approach like TCA\u0026rsquo;s concept of a view store + reducer) is often in charge of a great deal of backend logic, as well as mapping data from a third-party format to one your view is interested in. To construct your preview, you have to build all the third-party data which your live view would consume.\nLet\u0026rsquo;s break down the pain points:\nDifficulty in building the view for previews. Difficulty in distinguishing different preview configurations through code alone. Difficulty in reusing logic between previews, never mind constructing showcases like single-page mini-apps, or in-app showcase listings. \u0026ldquo;But wait\u0026rdquo;, you say! \u0026ldquo;I need to previews views with my actual view model, or else how do I know my view model works properly and everything works together?\u0026rdquo;\nIf you need this spelling out, here\u0026rsquo;s what you do:\nUnit test your View Model, like a good developer. Preview your View, the whole (set of possible inputs to the) View and nothing but the View. UI test the integrated whole - this will be even easier when you can spin up new targets like nothing, so bear with me. Step 1 - Views should be Views To build views in a truly testable way, you have to decouple the view from the view model. You could just add a protocol and call it a day, but you\u0026rsquo;ll find yourself dealing with extra boilerplate code that scales with number of views if you do. SwiftUI views are best maintained, and most performant, with many small views composed together.\nWhat does our view actually need? Let\u0026rsquo;s do the unthinkable, and start by banning View Models altogether.\n// View-agnostic model // [...] @Observable class ContentManager { // Formerly ContentViewModel var thingA: Int var thingB: String private var apiResponse: WhoppingGreatAPIResponse private var apiHandler: APIHandler init(thingA: Int, thingB: String, apiResponse: WhoppingGreatAPIResponse, apiHandler: APIHandler) { self.thingA = thingA self.thingB = thingB self.apiResponse = apiResponse self.apiHandler = apiHandler } var outputText: String { if apiResponse.countFoos \u0026lt; 0 { \u0026#34;Hello world! Your number is \\(thingA * 123) and your string is \\(thingB)\u0026#34; } else { \u0026#34;Negative foos shouldn\u0026#39;t happen\u0026#34; } } func submitRequest() async { await apiHandler.makeRequest() } } // View Model // View struct ContentView: View { var outputText: String var submitRequest: () async -\u0026gt; () var body: some View { VStack { Text(outputText) Button(\u0026#34;Do thing\u0026#34;) { Task { await submitRequest() } } } } } // Previews #Preview(\u0026#34;Success\u0026#34;) { ContentView(outputText: \u0026#34;Hello world! Your number is 123 and your string is ABCD\u0026#34;, submitRequest: {}) } #Preview(\u0026#34;Failure\u0026#34;) { ContentView(outputText: \u0026#34;Negative foos shouldn\u0026#39;t happen\u0026#34;, submitRequest: {}) } // Entry point for app, single page showcase app, in-app showcase listing, etc struct DataView: View { @Bindable var contentManager: ContentManager var body: some View { ContentView(outputText: contentManager.outputText, submitRequest: contentManager.submitRequest) } } See, that wasn\u0026rsquo;t so bad, was it? Our view model is still there, and look, our previews are so much cleaner than they were!\nContentView feels a bit like an extension of this new \u0026ldquo;DataView\u0026rdquo; at the moment, but this will be less so by the time we\u0026rsquo;re done. This is actually a crucial step, if you consider ContentView in this example represents our entire UI heirarchy - we\u0026rsquo;re moving our dependency injection closer to the entry point of our app. For more details on the benefits of this kind of approach, see \u0026ldquo;Achieving Loose Coupling with Pure Dependency Injection and the Composition Root Pattern\u0026rdquo; by Simon B. Støvring, from SwiftLeeds 2023.\nHowever, there are some flaws with this approach - our views will start needing a whole lot of parameters as we start to scale the things we need to access. Let\u0026rsquo;s solve that next.\nStep 2 - Making Models for our Views To scale to many parameters, we\u0026rsquo;re going to reintroduce View Models, but differently this time. Instead of being per-view, they\u0026rsquo;ll simply group values we often need to use together. And instead of being a class, they\u0026rsquo;ll be protocols, with only a Preview-prefixed mock defined alongside.\nNote that this is just a mechanism for grouping related parameters. We can, and should, still fall back to basic data types as the parameters for our views, and we can mix and match the two approaches to passing data. Use of the environment is also on the table, and this is a great fit for logging services which are needed everywhere, or for passing data transparently through multiple levels of view - but effective use of the SwiftUI environment is a wider discussion.\n// View-agnostic model // […] extension ContentManager: TextGenerator, IODoohickey {} // View Model protocol TextGenerator: Observable { var outputText: String { get } // Etc, with related stuff a view might want } @Observable class PreviewTextGenerator: TextGenerator { var outputText: String init(outputText: String = \u0026#34;Previews\u0026#34;) { self.outputText = outputText } // Let\u0026#39;s define common cases for use in previews! static let success = PreviewTextGenerator(outputText: \u0026#34;Hello world! Your number is 123 and your string is ABCD\u0026#34;) static let failure = PreviewTextGenerator(outputText: \u0026#34;Negative foos shouldn\u0026#39;t happen\u0026#34;) static let crazyLong = PreviewTextGenerator(outputText: String(repeating: \u0026#34;Rhubarb \u0026#34;, count: 200)) } protocol IODoohickey { func submitRequest() async // Etc, with related stuff a view might want } struct PreviewIODoohickey: IODoohickey { func submitRequest() async { // Do something interesting } static let noOp = Self() } // View struct ContentView\u0026lt;TextGeneratorType: TextGenerator\u0026gt;: View { var textGenerator: TextGeneratorType // Generic param - may have sub-models, may be used in property wrappers var ioDoohickey: any IODoohickey // No sub-models or property wrappers possible var body: some View { VStack { Text(textGenerator.outputText) Button(\u0026#34;Do thing\u0026#34;) { Task { await ioDoohickey.submitRequest() } } } } } // Previews #Preview(\u0026#34;Success\u0026#34;) { ContentView(textGenerator: PreviewTextGenerator.success, ioDoohickey: PreviewIODoohickey.noOp) } #Preview(\u0026#34;Failure\u0026#34;) { ContentView(textGenerator: PreviewTextGenerator.failure, ioDoohickey: PreviewIODoohickey.noOp) } #Preview(\u0026#34;Extreme data\u0026#34;) { ContentView(textGenerator: PreviewTextGenerator.crazyLong, ioDoohickey: PreviewIODoohickey.noOp) } #Preview(\u0026#34;No text\u0026#34;) { ContentView(textGenerator: PreviewTextGenerator(outputText: \u0026#34;\u0026#34;), ioDoohickey: PreviewIODoohickey.noOp) } #Preview(\u0026#34;Emoji\u0026#34;) { ContentView(textGenerator: PreviewTextGenerator(outputText: \u0026#34;😝\u0026#34;), ioDoohickey: PreviewIODoohickey.noOp) } // Entry point for app, single page showcase app, in-app showcase listing, etc struct DataView: View { @Bindable var contentManager: ContentManager var body: some View { ContentView(textGenerator: contentManager, ioDoohickey: contentManager) } } And just like that, we have the basics of reuse between previews, and we\u0026rsquo;ve even spun up some more because we can!\nNotice that ContentManager conforms to both these protocols. This allows sharing of state between each our view\u0026rsquo;s textGenerator and ioDoohickey, which could be useful if we needed the text update when we call submitRequest(). But because we\u0026rsquo;re no longer tightly coupled, we could equally decompose ContentManager into two separate types if that sharing of state isn\u0026rsquo;t needed. In fact, it\u0026rsquo;s encouraged with this pattern! Small views, and small models - it\u0026rsquo;s a software architect\u0026rsquo;s dream!\nOur view simply doesn\u0026rsquo;t care about backend logic at all under this model. Our backend types will have to conform and compose according to the needs of the View, not the other way around. If it comes to it, to conform to these protocols we may need new backend types, new layers of model, which is fine if you follow proper unit-testable practices.\nBut we can go one step further…\nPreviewProvider is dead. Long live PreviewProvider! By introducing a ContentView_Previews view (like we did in the days before macros), we can clean up some of the repetition introduced in the last step, while opening up more options for ourselves.\n// […] // Previews private struct ContentView_Previews: View { @Bindable var textGenerator = PreviewTextGenerator() var showControlPanel = false var body: some View { // Neat way to add a control panel to previews let view = ContentView(textGenerator: textGenerator, ioDoohickey: PreviewIODoohickey.noOp) if showControlPanel { ScrollView { view }.safeAreaInset(edge: .bottom, spacing: 0) { if showControlPanel { VStack { Divider() VStack { TextField(\u0026#34;Text\u0026#34;, text: $textGenerator.outputText) } .padding() .textFieldStyle(.roundedBorder) } } } } else { view } } } #Preview(\u0026#34;Success\u0026#34;) { ContentView_Previews(textGenerator: .success) } #Preview(\u0026#34;Failure\u0026#34;) { ContentView_Previews(textGenerator: .failure) } #Preview(\u0026#34;Extreme data\u0026#34;) { ContentView_Previews(textGenerator: .crazyLong) } #Preview(\u0026#34;No text\u0026#34;) { ContentView_Previews(textGenerator: .init(outputText: \u0026#34;\u0026#34;)) } #Preview(\u0026#34;Emoji\u0026#34;) { ContentView_Previews(textGenerator: .init(outputText: \u0026#34;😝\u0026#34;)) } #Preview(\u0026#34;Interactive\u0026#34;) { ContentView_Previews(showControlPanel: true) } // Entry point for app, single page showcase app, in-app showcase listing, etc struct ShowcaseEntry: View { var body: some View { ContentView_Previews(showControlPanel: true) } } struct ShowcaseMiniApp: App { var body: some Scene { WindowGroup { ContentView_Previews(showControlPanel: true) } } } struct LiveMiniApp: App { @Bindable var contentManager: ContentManager var body: some Scene { WindowGroup { ContentView(textGenerator: contentManager, ioDoohickey: contentManager) } } } struct DataView: View { @Bindable var contentManager: ContentManager var body: some View { ContentView(textGenerator: contentManager, ioDoohickey: contentManager) } } This sort of approach begs us to actively seek out more ways to use it! We just can\u0026rsquo;t stop reusing our view over and over again with subtly different configurations each time!\nGive it a try, and let me know what you think!\nAn aside - View composition You might struggle with the above, if you use a pattern where your View Model provides or manages the lifetime of child View Models. Without the View Models hiding all those dependencies, you may now find yourself passing 20 dependencies a View doesn\u0026rsquo;t need, just so it can make its child Views, and those child Views can make their own child Views, etc…\nThe solution? Inject the subview, not its dependencies.\n(This rule works for more effectively composing backend models too!)\n// View struct ContentView\u0026lt;TextGeneratorType: TextGenerator, ContentSubview: View\u0026gt;: View { // […] @ViewBuilder var contentSubview: () -\u0026gt; ContentSubview var body: some View { VStack { contentSubview() Text(textGenerator.outputText) Button(\u0026#34;Do thing\u0026#34;) { Task { await ioDoohickey.submitRequest() } } } } } // Entry point for app, single page showcase app, in-app showcase listing, etc struct DataView: View { @Bindable var contentManager: ContentManager var body: some View { ContentView(textGenerator: contentManager, ioDoohickey: contentManager) { ContentSubview(funkyDoodle: contentManager.funkyDoodle) } } } ","permalink":"https://mutatingfunc.github.io/blog/2024-04-28-effective-swiftui-previews/","summary":"Previews are a handy tool for getting a live look at all possible states of a view, and I often finding myself creating loads without thinking.\nYet I find people often have trouble getting previews to work nicely. This is my attempt to break down my approach for those people.\nIf you use TCA, which ties architecture very deeply to the view, my best advice is to look to their docs on the topic.","title":"Effective SwiftUI Previews"},{"content":"In my previous post I discussed how I prefer a few aspects of iPadOS over macOS. In this post, I\u0026rsquo;ll be discussing how to fix the big one - macOS\u0026rsquo;s window positioning.\nThe problem The fundamental means of positioning windows in macOS has never really received a rethink since the inception of windowing in the early days of the OS. It\u0026rsquo;s designed for screens with big pixels, and as pixels have gotten smaller it\u0026rsquo;s become increasingly fiddly to keep windows from being an untidy mess.\nYour browser does not support the video tag. This situation has lead to the creation of a whole array of third-party window managers, usually designed around use of keyboard shortcuts to position windows in various regions of the screen. I don\u0026rsquo;t get on with these - I prefer direct interaction. Workflows where new windows are being created usually involve the mouse, and swapping to the keyboard and back each time is an extra step.\nThird-party solutions There\u0026rsquo;s a few window managers I\u0026rsquo;ve tried which can be controlled via mouse.\nSwish, which allows swipes on a mouse/trackpad while pointing at a titlebar to position windows to halves and quarters, but you still have to reach for keyboard modifiers to achieve any sort of flexibility beyond that. BetterSnapTool, which allows creation of drop regions for positioning windows, but these are fragile and break when changing display or resolution. Swish is probably the window manager I\u0026rsquo;ve had the most success with, but I\u0026rsquo;ve never been able to shake the feeling that I\u0026rsquo;m fighting macOS, deliberately avoiding the built-in window positioning \u0026amp; resizing interactions. I always have to fall back to them at some point, especially now I\u0026rsquo;m using Stage Manager where Spotlight support for grouping windows is still missing on macOS.\nAmethyst There\u0026rsquo;s another category of window manager - automatic tiling window managers. This is the only kind of solution which doesn\u0026rsquo;t present an extra step every time you open a window, instead attempting to improve on default window behaviour.\nAmethyst is one example - windows are automatically arranged according to your choice of a wide range of layout rules. These include a large center window with columns either side, a left-hand primary window with a stack on the right, and even a hands-off \u0026ldquo;Floating\u0026rdquo; window layout which disables tiling entirely. It even lets you pull standalone windows out to ‘float’ them, and you can drag windows between ‘slots’ and even resize them, all using the native window interactions. It\u0026rsquo;s free, and open source, so anyone can add more layouts!\nUnfortunately, tiling windows has a few drawbacks. Windows can\u0026rsquo;t overlap - with professional apps, overlap is often useful, or even necessary, on smaller screens. Layouts often break down as windows hit their minimum size and start to break free from the layout, and you\u0026rsquo;re back to being in conflict with the native windowing system.\nEnter iPadOS 16 and Stage Manager…\niPadOS window management Your browser does not support the video tag. Using Stage Manager on iPadOS is a breath of fresh air. No longer do I have to fiddle with pixel-perfect window positions or sizes, because here windows snap to an invisible grid when other windows are nearby. Yet I still have total flexibility to arrange windows how I like, because I never needed this interaction to be pixel-perfect - having things neatly align is more valuable.\nThe interaction is a callback to the context in which pixel-perfect windowing was meant to work, back when everyone used 800×600 screens, with big, visible pixels. Freeform windowing was easy then, pixels were big visible squares and windows aligned to the pixel grid, so even our clumsy hands could manage a neat arrangement.\nBy introducing an alignment grid, iPadOS has captured the context that freeform windows were designed around, that has been lost as displays improved. And now, once again, arranging windows can be something neat and orderly, intuitively playful.\nFixing macOS windowing So now we\u0026rsquo;ve hit upon a solution, right? If windows on macOS could snap to a grid too, maybe we could finally go from fighting the native interactions of the platform, to embracing them. Arranging windows on the fly would be a breeze!\nSo Apple will bring this to macOS right?\nAnother year. Another macOS release. Still, we\u0026rsquo;re craning our necks, gripping our mice for precision to try and perfectly align two windows, because macOS can\u0026rsquo;t be intuitive, playful, neat, and orderly like iPadOS.\nPeople use it for work. It has to be this way.\nAnd that\u0026rsquo;s that.\n…\n…\n…\nOr is it?\nIs there some crazy idea we missed?\n…\nIf Amethyst layouts can tile some windows according to a preset layout while allowing others to float, doesn\u0026rsquo;t that mean they\u0026rsquo;re arranging windows in a way which supports overlap?\nAnd if dragging or resizing a window can be a trigger for Amethyst to reflow the layout, doesn\u0026rsquo;t that mean it can fully integrate with native interactions?\nSo couldn\u0026rsquo;t a custom-made layout act like the Amethyst\u0026rsquo;s freeform \u0026ldquo;Floating\u0026rdquo; layout, but just nudge windows ever so slightly so that they always align with an invisible, ~64pt grid?\nYour browser does not support the video tag. Yes, yes it could. And it\u0026rsquo;s glorious.\nYour browser does not support the video tag. Check out the PR for updates!\n","permalink":"https://mutatingfunc.github.io/blog/2023-11-03-macos-amethyst-window-snapping/","summary":"In my previous post I discussed how I prefer a few aspects of iPadOS over macOS. In this post, I\u0026rsquo;ll be discussing how to fix the big one - macOS\u0026rsquo;s window positioning.\nThe problem The fundamental means of positioning windows in macOS has never really received a rethink since the inception of windowing in the early days of the OS. It\u0026rsquo;s designed for screens with big pixels, and as pixels have gotten smaller it\u0026rsquo;s become increasingly fiddly to keep windows from being an untidy mess.","title":"Fixing macOS windowing"},{"content":"I have opinions. 🐣\nI tend to prefer working on iPadOS to macOS. There\u0026rsquo;s not any one thing, but a combination of things which just make it feel more comfortable to work with… It\u0026rsquo;s fair to say macOS has features which iPadOS is lacking, but the reverse is also true!\nWindowing There’s no weird distinction between maximised and fullscreen windows. (And let’s not get into how some apps don’t maximise, but “zoom”) We don’t work all the time, and iPadOS defaults every app to be fullscreen by default when Stage Manager is disabled. 👌 In comparison, macOS just makes a mess if you do the same, and still opts to waste 80% of your screen by default. Where in macOS a window would get lost behind another, iPadOS lets windows peek out from behind like a tab, ready for further use. This encourages layering of windows, where macOS would discourage! iPadOS snaps windows to a neat \u0026amp; orderly grid, with enough snap guides that you can still size for exactly as much content as you want, so everything looks clean and you can get on with work. With macOS, you’d better be ready to whip out a magnifying glass to avoid adjacent windows having a 3 pixel height difference. Design The animations are so good… ✨ The cursor is a nice and symmetric circle, clearly defining the interaction to perform. Not a miniature cap for a zebra! 🦓 Don’t like the pointer effects? You can turn them off in Settings! “Oh, but for what I do I need pixel-perfect precision” - No, you don’t. If you’re using your cursor for this, you’re doing it wrong. Apps have snap guides and text fields for this sort of thing. Minimal window chrome, and windows adopt an iPhone-style compact layout when small! And this has real benefits for side-by-side multitasking with many windows on 4k/built-in displays. Power-user features Shortcuts automations. Amazing utility on iPadOS, missing on macOS. Admittedly, 3rd party options are available on macOS for a price, and a fair bit of research \u0026amp; discovery time. What are automation triggers good for? Try pairing dark mode with Do Not Disturb for use in lectures, making your text size larger at night, or playing a startup chime when connecting your external display! 🎵 iPadOS’s share menu has useful options like “Copy” and “Save to Files”. It’s also a powerhouse when loaded with custom-made Shortcuts. On macOS, you sometimes have to break context to save content from an app to the filesystem via copy \u0026amp; paste, or depending on the app you may have to add it to the filesystem to get it into a Shortcut! Native iPhone apps. But all of them, with touch, not like in macOS where 99% of developers opt-out and many of the rest don’t work.. Hardware Rear-facing camera, good for grabbing the diagram from a slide when taking notes! Touchscreen. Detachable screen for content consumption. Apple Pencil support. Wonky side-on camera, yes, but it’s at crystal-clear resolution! A case for macOS Offline backups to external drives. Backing up your iCloud-synced files to iCloud is not a backup at all! Macs are currently a backup-making device, first and foremost. If you’re a developer, IDEs exist beyond Playgrounds. At least until Apple releases CompilerKit, their ‘more secure’ take on code generation which meets App Store guidelines, and/or get round to an Xcode rebuild using a SwiftPM-based project format prototyped in Playgrounds Accessibility APIs so you can use window managers as a crutch for your subpar windowing experience. Command line… Are you sure you couldn’t do the same stuff just as fast with a UI designed for humans, theoretically? Keyboard shortcuts are generally faster than typing words. [Your niche use-case here] I\u0026rsquo;ve delibrately left out the menu-bar here. iPadOS\u0026rsquo;s keyboard shortcuts overlay serves a similar purpose. The menu bar has some benefits, but it\u0026rsquo;s also a good way to have undiscoverable functionality - out of sight, out of mind.\nOverall, I often get the impression iPadOS is the platform people choose to use, while macOS is the platform people need to use. And it makes sense - iPads can be used like a Mac, docked to an external display with a mouse and keyboard, or like a tablet, or like a notepad with a stylus! That\u0026rsquo;s 3 different modes of computing, and visionOS presents a fourth, and realistically people may prefer to work in any one of these.\nHopefully as Apple\u0026rsquo;s shared OS codebase matures, the need for separate devices running macOS to support the rest will continue to fade away! Because as computers start coming in all sorts of shapes and sizes, why should certain features like offline backups be tied to any particular kind of device and input method?\n","permalink":"https://mutatingfunc.github.io/blog/2023-10-31-ipados-vs-macos/","summary":"I have opinions. 🐣\nI tend to prefer working on iPadOS to macOS. There\u0026rsquo;s not any one thing, but a combination of things which just make it feel more comfortable to work with… It\u0026rsquo;s fair to say macOS has features which iPadOS is lacking, but the reverse is also true!\nWindowing There’s no weird distinction between maximised and fullscreen windows. (And let’s not get into how some apps don’t maximise, but “zoom”) We don’t work all the time, and iPadOS defaults every app to be fullscreen by default when Stage Manager is disabled.","title":"iPadOS is better than macOS (for normal people)"},{"content":"A mutating and functional blog for whatever is on my mind. Expect Swift and iPad topics published from my iPad!\n","permalink":"https://mutatingfunc.github.io/blog/2023-10-16-firstpost/","summary":"A mutating and functional blog for whatever is on my mind. Expect Swift and iPad topics published from my iPad!","title":"Welcome to my blog!"},{"content":"Keybuild Build your own keyboard!\nTouchscreen keyboards don\u0026rsquo;t need to be one-size-fits-all, so let\u0026rsquo;s change that! With Keybuild, you choose the layout, the keys, everything.\nKeys can be set up to type whatever you want! Letters, greek letters (ɑ, β, ɣ), maths symbols (π, ∫, ∑), arrows (↑→↓←), or even your favourite emoji can be added and organised depending on your needs.\nCreate multiple ‘panes’, each containing keys arranged using a stack-based layout, and switch between them with either an automatic menu, or direct links.\nAdd a small number row to your Qwerty keyboard, or have a try using Azerty or Colemak layouts. Build a keyboard that can keep up with your maths-heavy university course, or specialist profession. Or just make a dashboard for your favourite emoji. Or do all that and more, it\u0026rsquo;s up to you!\nhttps://apps.apple.com/app/id1547174534\nMedlied - Media Library \u0026amp; Editor Medlied is an app for managing your local music \u0026amp; video library of MP3 and MP4 files, with support for editing file metadata via long-press / right click. Music can be added to the queue from your library, or from external sources, with options to shuffle and loop tracks.\nDesigned to avoid the need to use a Mac to edit one’s local library, edits can be made in-place, and files can be stored anywhere.\nEditable file metadata currently includes Title, Album, Artist, Track and Disk Numbers, Artwork, and Lyrics. Unchanged metadata will be preserved when editing.\nhttps://apps.apple.com/app/id1606367519\nShimmer Browser Easily navigate between a set of frequently accessed tabs!\nTabs are synced via iCloud, and open using an in-app browser so all your passwords and blockers are available!\nAdd your tabs to your homescreen with the widget!\nhttps://apps.apple.com/app/shimmer-browser/id6739163018\nSimpleEdit A simple plain text editor built using SwiftUI.\nFeatures:\nOpen any text file or compatible data files as UTF8 encoded text from the Files app. Enter view-only mode for data detection (links, phone numbers, addresses, …). Rewind button to revert files to their original state. Supports dark mode, multiwindow, and state restoration. Customise font family \u0026amp; size used for editing. Select fonts using the system font picker, which shows fonts installed in the Settings app. (Settings → General → Fonts) Switch between native iOS keyboard types (default, number pad, phone pad, URL, web search, Twitter, …). https://apps.apple.com/app/id1287562515\n","permalink":"https://mutatingfunc.github.io/apps/","summary":"Keybuild Build your own keyboard!\nTouchscreen keyboards don\u0026rsquo;t need to be one-size-fits-all, so let\u0026rsquo;s change that! With Keybuild, you choose the layout, the keys, everything.\nKeys can be set up to type whatever you want! Letters, greek letters (ɑ, β, ɣ), maths symbols (π, ∫, ∑), arrows (↑→↓←), or even your favourite emoji can be added and organised depending on your needs.\nCreate multiple ‘panes’, each containing keys arranged using a stack-based layout, and switch between them with either an automatic menu, or direct links.","title":"Jamie's Apps"},{"content":"What data does Shimmer Browser handle? Shimmer Browser stores the user\u0026rsquo;s saved tabs in iCloud, and settings either locally on-device or on iCloud.\nRefer to Apple\u0026rsquo;s privacy policy for information about how your iCloud data is handled.\nWhat doesn\u0026rsquo;t Shimmer Browser handle? Shimmer Browser does not transmit any data for tracking or marketing purposes.\nResponsibility The privacy and security of any user-provided data stored by Shimmer Browser is the responsibility of the user, for the following reasons:\nShimmer Browser does not encrypt data, or attempt to limit access. Shimmer Browser allows export of unencrypted data to other apps and devices. Shimmer Browser is not intended to store confidential or personally identifying information. ","permalink":"https://mutatingfunc.github.io/shimmerbrowserprivacy/","summary":"What data does Shimmer Browser handle? Shimmer Browser stores the user\u0026rsquo;s saved tabs in iCloud, and settings either locally on-device or on iCloud.\nRefer to Apple\u0026rsquo;s privacy policy for information about how your iCloud data is handled.\nWhat doesn\u0026rsquo;t Shimmer Browser handle? Shimmer Browser does not transmit any data for tracking or marketing purposes.\nResponsibility The privacy and security of any user-provided data stored by Shimmer Browser is the responsibility of the user, for the following reasons:","title":"Shimmer Browser - Privacy"}]