SwiftUI can be really annoying when you try to do basic UI customization. Consider this list:
đ
Letâs say you want to get rid of the left padding of the separators. After some googling (which is another problem because Google thinks you want to remove the separators completely), you find the .alignmentGuide modifier with .listRowSeparatorLeading. So you add the code:
.alignmentGuide(.listRowSeparatorLeading){_in-100// set it to -100 just to be safe}
But the navigation barâs separator still has the padding:
đ
You donât want to switch to .listStyle(.inset) because that would remove the navigation barâs separator. After more googling, you find nothing useful. But setting .toolbarBackground to .visible will make the separator full-width. So you add the code:
.toolbarBackground(.visible,for:.navigationBar)
The navigation bar now has an off-white background color, but you can live with that. The separator seems to be full-width!
⊠until you pull down the list:
đ«
After even more googling, you cannot find any way to remove the left padding of this first separator. The closest thing you can do is to use a .listSectionSeparator modifier to hide it. So you do that:
.listSectionSeparator(.hidden,edges:.top)
Of course, the âreal SwiftUI wayâ is to not remove the left padding of the separators. But sometimes you just want things to look a certain way, right? And thatâs where SwiftUI can really, uhh, test your patience.
Unprocrastinator is a Safari extension that makes you wait 30 seconds before you can see some websites.
Welcome back to my regular program of making weird apps.
Lately, manyplatformshaveturned, uhh, not-so-nice to use, and a lot of people are saying they want to stop using them. Now you can do just that with Unprocrastinator. Unprocrastinator puts a 30-second delay on your chosen websites. Itâs like a mini time-out for your brain before you dive into the internet rabbit hole.
After years of trying various methods, I broke this habit by pitting my impatience against my laziness. I decoupled the action and the neurological reward by setting up a simple 30-second delay I had to wait through, in which I couldnât do anything else, before any new page or chat client would load (and only allowed one to run at once). The urge to check all those sites magically vanishedâand my âproductiveâ computer use was unaffected.
The 30-second delay is not customizable on purpose. Itâs long enough to be a little annoying, but not so long that youâll want to turn off the extension right away.
Note: When you first use Unprocrastinator, try not to add too many websites all at once. It could end up being more frustrating than helpful.
Unprocrastinator Pricing
Unprocrastinator costs $0.49 on the App Store. (Iâm trying out the new App Store price points.) It does not include any subscriptions, in-app purchases, ads, or tracking. Itâs a universal purchase, which means once you buy it, you can use it on all your Apple devices.
Unprocrastinator Privacy Policy
Unprocrastinator does not collect, store, or transmit any personal information.
Unprocrastinator Support
If you have any questions, you can contact me via email or Mastodon.
Recently, YouTube has been ramping up its anti-adblock effort, and Iâve been watching this closely. This blog post is where I write down what I know.
Some Background
Hereâs how adblockers (used to) block YouTube ads. Before playing a video, YouTube would check its API, and the server would send back something like this:
And the adblockers would override JSON.parse and Response.json to make it return this instead:
{"video":"something.mp4","ads":[],"etc":{...}}
This trick worked for a few years. But earlier this year, YouTube started making fake requests to see if the responses were changed. If the responses were changed, it meant the user was using an adblocker.
Most adblockers stopped working, but a few like uBlock Origin and AdGuard updated their filters to avoid these fake requests. Then YouTube would update their fake requests so the adblockers would fall for them. Itâs been a game of cat and mouse between YouTube and the adblockers ever since.
The Tech Support Hell
YouTube isnât rolling out the anti-adblock to everyone. It seems to depend on things like your account, browser, and IP address. And if youâre not logged in or youâre in a private window, youâre safe. As a result, there are a bunch of people saying, âI use XYZ and I havenât seen an anti-adblock popup yet,â unknowingly spreading misinformation.
But hereâs the thing: YouTube isnât just targeting adblockers. Use Privacy Badger? Youâll get flagged. Use Malwarebytes? Youâll get flagged. Set your Edge browserâs tracking protection to âstrictâ? Yep, youâll get flagged. So a lot of people think their extensions are safe to use, but actually theyâre not.
And contrary to what you might think, using multiple adblockers can actually make things worse. Thatâs because all your adblockers need to be up-to-date to dodge YouTubeâs detection.
As you can imagine, this is creating a tech support nightmare if youâre part of an adblocker team.
The Redditors
On Reddit, the uBO team put up a detailed post on how to handle YouTubeâs anti-adblock. But many people donât actually follow it. Youâd see people saying, âI did what the post said but Iâm still having issues.â But when theyâre asked to share their system info for troubleshooting, it turns out they didnât really follow the post.
Then there are non-tech-savvy users looking at the post and saying, âThis is too complex. I give up.â
Then there are tech-savvy users who say, âYour filter has CODE in it. Thatâs risky. Can you explain what it does? I donât want to run anything I donât understand.â
And of course, thereâs always the classic âIVE TRIED EVERYTHING AND NOTHING WORKS HELP!!!!â
All this noise makes it hard to find any useful info.
The Stupid Filters
Some people have been sharing custom filters that use CSS to hide the popups. But thatâs like sticking your head in the sand. Because the popups donât stop there: first, itâs just a friendly warning, then you can only close the popup after a delay, then you get a âthree strikes and youâre outâ popup. And finally, no more popups are shown, but at this point, you canât play videos anymore.
Recently, someone shared a filter on Twitter that literally had code to set adBlocksFound to 0. Itâs as if they think YouTubeâs anti-adblock works like this:
if(adBlocksFound>0){blockUser();//!!!!!}
That tweet got super popular. And it probably led a lot of people to add those filters to their adblockers. This has really piled on the work for the troubleshooting team. Did the person who shared it not realize it was harmful? Or did they just care about getting likes?
The Script ID
Every time YouTube tweaks their script, part of the URL changes. This part is what uBO calls the ID, and they have a webpage that keeps track of the latest one.
But hereâs where things get messy. Some people think this ID is what they need to block. Some even suggest ways to automate the process (like, âWhy donât you just block that with REGULAR EXPRESSION?â)
Another issue is that sometimes YouTube pushes out an update that has nothing to do with adblock. But it still changes the ID. Then you get people saying, âThe ID changed, why hasnât this post updated yet???â
The Moderator Who Quit Reddit
All this resulted in a ton of pressure on the uBO team members who were trying to help out in the thread. One by one, I saw them say theyâd had enough of the comments and werenât going to reply in these threads anymore.
And then one of the moderators actually deleted their Reddit account. âThe ID in the post wasnât updated because my mother was hospitalized,â they said.
Itâs sad to see them leave because of some drive-by comments â new users who sign up for Reddit, leave their comments, and then delete their accounts without facing any consequences.
Sure, there are people who appreciate what the uBO team is doing. But the hurtful comments leave a bigger mark than the good ones.
The War of Attrition
Since May, uBO has been in a cat-and-mouse game with YouTube. And theyâve shown incredible resilience, especially when you consider that there are only two people on the uBO team dealing with YouTube.
The uBO team members are all volunteers. Theyâve gone above and beyond to meet every little request from their users. But thereâs a limit to how much they can take. At some point, the constant demands become too much, and they will leave uBO for good. Itâs one thing to play cat and mouse with YouTube. Itâs quite another to deal with a wave of angry users.
Maybe thatâs how YouTube will win this war of attrition.
TLDR: Create a folder and move your saved pages into it.
Sorry about the âone weird trickâ title đŹ
If youâve been using History Book for a while, you probably have thousands of saved web pages. This can really slow down the application. I suspect this might be due to SwiftUI somehow not lazy-loading all items, but Iâm not 100% sure.
The good news is that you donât need to load all the web pages to use History Book. The search works across all folders, so you can organize your saved web pages in any way you like.
(I considered adding an official Archive and Trash folder in History Book, but I didnât want to impose any specific folder structure on users.)
Anyway, hereâs how to do it:
Create a new folder and name it Archive or something.
Go back to your History folder with 15,000 pages.
Press Command+A to select all the pages.
Go do something else while the cursor beach-balling, itâs gonna take a while.
Click the Move to⊠dropdown in the toolbar and select the new folder.
Thatâs it! You can now enjoy a more efficient History Book.
Note: if you have enabled the auto-remove webpages option, History Book will not delete the old pages in the Archive folder. If you prefer to do this manually, you can create a new folder each month and name them â2023-01â, â2023-02â, etc., and delete the folder when theyâre too old.
Long time no see, itâs been a while since I last wrote anything. Sorry for my absence â I took some time off to deal with a burnout.
What happened
So long story short, I stupidly tried to create a custom video player that looks like the Safari video player, but with features like chapters support, showing thumbnail images when you mouse over the progress bar, and other customization options. It turned out to be too difficult for me. But due to sunk cost fallacy, I continued my attempts.
Since I canât promote my apps on Twitter now (more on that later), I wanted the video player update to be a big, newsworthy one. The problem was, the more time and effort I put into it, the longer I neglected to update my existing apps, and the more their quality deteriorated. Then I felt more pressured, like âI really have to deliver this thing now.â Then I began to lose self-confidence.
Eventually, I found myself with no motivation left for my work. Although my brain knew what needed to be done, my body just couldnât follow through. Itâs like a self-fulfilling impostor syndrome, and now I am the impostor.
Other stuff
Then I started worrying about the business-y part of my business.
Twitter (and sometimes Reddit) used to be my go-to place for announcing my apps and talking to users. It was a valuable platform for small-time indie developers like me. But then Twitter changed their owner, Reddit enshittified their platform, Mastodon is niche, Bluesky is invite-only, and Threads⊠just doesnât seem like the right fit. Iâm left wondering: Where do I go from here?
I also thought about the potential unsustainability of my business model. My apps are paid upfront, and Iâve tried to keep their prices (I think) reasonably affordable. And I donât charge anything for updates. However, this means Iâll have to rely on a steady influx of new users to sustain my business. I mean, I wonât switch Vinegar to a subscription model, but I just feel concerned about whether I can maintain this approach?
I canât tell you how many ideas Iâve had for other content-blocking methods and ways to make the web more tolerable. I hate the modern web. But if I made an app that was like made the web more tolerable, it would feel like janitorial work to me. Now every day I have a new pile of crap that the web has given me that I have to figure out how to deal with. And thatâs just like a negative life for me. I donât want that.
This really made me think: most of my apps fall into the âmake the crappy web tolerableâ category, and sometimes, it does feel like janitorial work. Do I want that?
I donât have the answers to any of these questions.
Now what
I still havenât recovered from the burnout. But I shipped an update to History Book a while ago, so yay me? Iâll now focus on shipping some inconsequential updates to Vinegar next and slowly ease my way back. I hope I made it.
Iâm not launching a new app. This is just a simple guide for developers of Mastodon apps.
On iOS, the âopen in Xâ behavior is typically implemented with Universal Links. However, since Mastodon is decentralized, there is no official list of URLs developers can use. Fortunately, we can create a simple Safari extension to work around this issue.
âI call on the power of the Mastodon!â
Creating the Safari Extension
Register a custom URL scheme for your app from the Info tab of your project settings.
Create a new Safari extension by choosing File â New â TargetâŠ, then choose Safari Extension.
In the manifest.json file, update the "content_scripts" property to match every web page.
"matches":["http://*/*","https://*/*"]
You can also remove the "service_worker" and the "default_popup" properties if you donât plan to use them.
In the content.js file, paste in the following code:
// A more reliable way is to check for an API response,// e.g. https://domain.name/api/v1/timelines/public?limit=1// but that requires an extra round trip to the server.// So we just do something quick and dirty:if(document.querySelector('[id*=mastodon]')){// If you want to open your app only from the profile and post pages,// you can add an additional if statement.// if (location.pathname.match(/@(.+?)(\/|$)(\d+)?/))// The regex in this statement also lets you access the username and post ID,// in case you want to use a custom URL format.location.href=`blackranger:${location.host}${location.pathname}${location.search}`;}
Thatâs it!
Note: You will also need to handle the incoming URLs in your app by implementing the application(_:open:options:) method in your AppDelegate. If your app uses SwiftUI, you can use the onOpenURL(perform:) view modifier to handle the URLs.
Customizing the Extension
You should update the extensionâs name and description in the messages.json file.
You should also add some icons for the extension. Use your appâs icon as the basis for the extensionâs icons, but remember to add the rounded corners yourself. The toolbar icons should be monochrome PNG files with an alpha channel.
To ensure that the extension is effective, you should instruct your users to enable the extension and grant it permission to access all websites.
As an advanced option, you can add the domains of Mastodon URLs to a database when opening the app from those URLs. This will allow you to navigate directly to the appropriate view in your app when the user taps on a link without going through the app-Safari-app hoop.
The source code for this project is available on GitHub, and Iâm happy to answer any questions you may have. You can reach out to me on Mastodon or via email.
Rewinder is a Safari extension that lets you go back in time and see how websites looked in the past.
Okay, but thatâs just marketing-speak. Rewinder is actually a front-end for the Wayback Machine API, and its UI is inspired by Time Machine. But instead of using arrow buttons, you can navigate through different snapshots by simply scrolling the page.
(Not shown: the time it took to load all of these snapshots.)
Youâre probably already familiar with Wayback Machine, so I wonât repeat its benefits here. (In case youâre not familiar with Wayback Machine, hereâs a blog post generated by ChatGPT for you.)
Keep in mind that archive.org is very slow, so refresh the page if snapshots take too long (say, longer than 1 minute) to load. And avoid opening multiple archive.org pages too quickly, or you may encounter the 429 Too Many Requests error.
Rewinder Pricing
Rewinder is available for $1.99 on the App Store with no subscriptions, in-app purchases, ads, or tracking. Plus, itâs a universal purchase, so you only need to buy it once to use it on all your Apple devices. I hope you find Rewinder useful.
Rewinder Privacy Policy
Rewinder uses the Wayback Machine API, so check out their privacy policy. The Safari extension does not collect, store, or transmit any personal information.
Rewinder Support
If you have any questions, you can contact me via email or Mastodon.
Medley is a music player that looks like the pre-iOS 6 Music app.
The Backstory
A few weeks ago, I was dealing with some SwiftUI bugs in the iOS 16 beta.
The toolbar was buggy, so I replaced it with a custom view. The navigation bar was buggy, so I replaced it with another custom view.
Then it suddenly hit me: if I can replace the navigation bar and toolbar, I can probably build an iOS 6 style UI with SwiftUI. Then I added background images and text shadows to my custom views, and it just⊠worked.
Sidenote: SwiftUI and Customization
The system components are notoriously hard to customize in SwiftUI. Like youâd get 4 methods for customizing something, and if they donât do what you want, thatâs it. The navigation bar and toolbar are probably the hardest to customize because theyâre not even Views in SwiftUI. (They get automatically generated when you use some modifiers.)
So if I can do without the navigation bar and toolbar, I should be able to build the rest of the iOS 6 style UI with relative ease.
Anyway
So I wanted to make an app that looks like a pre-iOS 7 app. Ideally, it should have a healthy mix of images and text in the UI to keep it interesting, it shouldnât require a lot of text input because the flat keyboard will break the illusion, and it shouldnât be a UITableView snooze-fest. In the end, I decided to make a music player. I call it Medley.
Medley is not a pixel-perfect re-creation of the Music app. For example, it uses San Francisco instead of Helvetica, and almost all the icons are SF Symbols. It doesnât have Cover Flow because Iâm not sure why Apple discontinued it after the patent suit.
Thereâs a Cover⊠Wall thingy, though.
You can play music from Apple Music if youâve already added them to your music library. But Medley doesnât support browsing the Apple Music catalog or adding songs from Apple Music into your library. I hope youâll like it.
Medley Pricing
Medley is available for $2.99 on the App Store, with no subscriptions, no in-app purchases, no ads, and no tracking.
Currently, Medley is only available on the iPhone. I will consider making an iPad version if thereâs enough interest.
Medley Privacy Policy
Medley does not collect, store, or transmit any personal information.
Now you can also track your monies like itâs 1979!
Monies is a very basic expense tracker app I made for my wife. It looks like a spreadsheet because sheâd been using Excel to track her expenses. But itâs like a spreadsheet on⊠whatever the opposite of steroids is.
The whole point of the interface is to let you add items quickly. Just tap anywhere to start editing, then tap next on the keyboard to jump to the next cell. To delete an item, just clear its price and name, then tap Save.
There are no fancy features like linking bank accounts, charts, or even a settings screen. It is available for free on iPhone and iPad, and it syncs via iCloud. I hope you find it useful.
Monies Privacy Policy
Monies does not collect, store, or transmit any personal information.