Frederik Handberg's avatar
Frederik Handberg
npub1nj0c...2gqz
23 🇩🇰 I'm studying for a degree in Software Engineering while building fun projects and experimenting with drone technology. I'm also learning to sew garments as a hobby 🪡 Sometimes I work as a freelance photographer for different news outlets 📷 I mostly share progress updates as I'm working on my software and drone projects, and as I learn to sew. Basically, I just write about my hobbies...
I am mostly done implementing support for all the canvas objects in the Electron app. So I’m moving on to note documents now because the last object I need to support in the canvas is the note docs. I have just gotten Lexical to work in the editor. The experience is very different than my native Swift app. This is because the native Swift app uses a separate text view for each block whereas all blocks are just one continuous text view in Lexical which is much better for performance. I have not yet implemented support for images and videos. Only text with formatting is working like paragraphs and headings. #dev
Have been using Electron for the past few days. Just learning the basics and playing around. I have implemented a few object types for the visual canvas of my notes app. Images and videos are working nicely. I needed to learn Electron protocols to fetch local image and video files from storage. Text objects are also working with shortcuts to apply formatting. I used Lexical for the text styling. I will say that getting gestures to feel native is really difficult. For example, JavaScript does not expose when the user finishes a gesture. Like, if I swipe, then there is no API in JavaScript that tells when the user lifts there fingers. I have been trying to solve a problem called "inertial scrolling". Ended up using a package called 'lethargy' that does fix the issue, but it caused it's only issue by being a bit glitchy at times. https://www.npmjs.com/package/lethargy Anyways, my point is that it's a whole lot easier to get smooth native gestures in #AppKit and #SwiftUI than Electron. View quoted note →
I'm trying Electron for the first time. For the past many months, I have been working on a notes app for macOS. I decided to build this app using native tools for macOS, which is the Swift programming language with SwiftUI and AppKit. In the future, I want the app to be made for multiple platforms as I personally use different devices running different operating systems. Building a native app makes this problematic as I then need to build a completely new app for each platform. That's going to take a massive amount of time as a single developer, even with AI to help... For example, for iOS I would need UIKit, for Android it would be Kotlin with JetPack, for Windows it would probably be a C# framework, and for Linux it would most likely be some C or C++ framework. Electron would immediately work for Windows, Linux, and macOS. My reason for choosing the native route was just to experiment and learn, and I'm happy I made that decision. I have learned a lot. Regarding SwiftUI and AppKit, I have found the developer experience to be somewhat disappointing. The documentation has not always been the best and online resources are quite limited. Performance has also been a problem with SwiftUI and same with customizability. When implementing a new feature, I usually started out in SwiftUI and then I learned that what I wanted to build wasn't possible in SwiftUI, so I ended up having to use AppKit anyways. And don't even get me started on AppKit. It's old. Very old. And you can easily feel that. I find that features take a long time to get perfect with AppKit. I usually have some bugs that I end up spending way too much time trying to solve. It also was not uncommon for me that I had to use some "low-level" APIs from AppKit. This is where it got really difficult, because as already stated, the official documentation isn't always very informative. **Let me explain my native macOS dev experience with a bug that I experienced:** Try to insert an `NSTextView` and then place a SwiftUI view above it. Then try to change the `NSCursor` to arrow when hovering the SwiftUI view. You will notice that the cursor is NOT the arrow, but instead `iBeam`. This is because `NSTextView` is super aggressive with overriding the cursor no matter whatever you do. You wouldn't believe the amount of time I have spent trying to solve this bug and others alike... This has been my experience with AppKit and SwiftUI. I feel like I have spent more time solving bugs than implementing features - that's an exaggeration, but still, you get the point :) This sort of stuff just works by default in Chromium. Instead of solving stupid bugs, you get to actually implement features.
It's now possible to search in canvas files 🎉 I added a new search feature to my notes app for macOS. When I click `Enter` it will select the next result in the list and `Shift+Enter` will select the previous result. The problem is that the list does not scroll up and down to always keep the selected result visible. That's the last thing to fix and then canvas search should be working fully as expected. #dev #Swift #SwiftUI #AppKit #macOS
I wanted to conditionally show and hide the playback controls of the AVPlayer in AppKit. I did this by providing a decimal, so when the value was below a certain number, I would hide the playback controls. This did not work quite as expected… Even though the playback buttons did get removed, there was still a blurry rectangle that I could not seem to get rid of. I did some debugging by printing all the views on screen to figure out exactly what is showing. I needed to know the names in order to “kill” those views. I figured out the two layers was called `EventPassthrough` and `MovableView`. Now I could explicitly hide those. I made a small GitHub repository to share the solution:
I'm doing quite a big refactor... Basically, the app was destroying ViewModels whenever the user switched to a different tab. **For example:** - The user opens a note document. Now a ViewModel called `NotesViewModel` has been created. - The user now opens a canvas file which now creates a `CanvasViewModel`. Because the user opened a canvas file, the app automatically switched to a new tab with the canvas file. Because of the way my app was engineered, it would destroy the `NotesViewModel` for the note document. Then once the user switches back to the tab with the note document, it would create the `NotesViewModel` once again. This is far from ideal, so now I'm refactoring the code to not destroy ViewModels unless the tab has been closed. This should also improve performance a lot when switching between tabs as it does not need to recreate ViewModels all the time... #dev #AppKit #SwiftUI
In my notes app, I have overused notifications to pass data around my application. More specifically I used `NotificationCenter`. Using a notification-based system is certainly a very fast and easy way to pass data around, but it’s quite difficult to debug. This is the reason why I’m refactoring parts of the code to be using a state-driven approach in my ViewModels.
I’m building the app that I haven’t been able to find: a beautiful and native notes app for macOS that is perfect for writing note documents and creating mood boards in an infinite canvas. image As a software developer, I need the functionality to create and edit Markdown files to write the features my applications need. I include implementation details for the features I’m building that I can then use to prompt LLMs with. I also do a lot of frontend work, so I’m often looking for inspiration. I think that an infinite canvas would be perfect for this, where I’m able to import images and videos of interesting UI designs that I find on socials. I also started a new hobby: fashion design. In the screenshot of the app, I imported some images and videos into the canvas of some garments that I find cool. #dev #Swift #SwiftUI #AppKit #macOS
Link previews and interactive website objects are now fully implemented in the notes app. #dev image I ended up changing the website objects so that by default they use snapshot images when they aren't focused. Then once the user focuses a web object, it changes from snapshot to a `WKWebView`, so that the user can interact with the website. When unfocusing, the object will then convert back to a snapshot again. However, the app will take a new snapshot before the conversion from webview to make sure it's up to date.
I'm investigating a bug where files are getting copied rather than moved when I press and drag a file from one folder to another. It's a super annoying bug... #dev #bug
I will begin #sewing the next t-shirt. The latest one I made was almost perfect. The shoulder seams should’ve been a bit longer, so I increased that by 2 cm on both sides. When I have made the perfect t-shirt that fit me, I can move on to the next project. This will most likely be a sweatshirt. I’m already working on the pattern in #CLO3D. image
Been working on copy-paste functionality for my notes app. It may sound a bit strange as you might expect this to just work by default, but because of the way I handle text editing in the app, it does not work out of the box. My notes app is block-based and because of this, each text block is its own text view. This means, a note document includes multiple `NSTextView`s (one for each text block). This makes copy-pasting from multiple blocks quite complicated as these text blocks are completely separate - they have no connection to each other. This is where my ‘sequential text view’ hack comes into play. I figured out a way to connect separate `NSTextView`s to allow performing text selections across multiple text views, and then I override the default `copy()` function with custom logic to stitch these text blocks together while keeping the text styling/formatting. So far, copy-pasting works for text, heading, and list blocks. Tomorrow, I will make it work for table, quote, and code blocks.
Using `Text()` from #SwiftUI has turned out to be a bit of a problem - at least in the visual canvas. In SwiftUI, the easiest way to zoom in on a view is to use `.scaleEffect()`. This is exactly what I did in the canvas, so when you zoom in on a text object, I would increase the text size by simply using a `.scaleEffect()` modifier. However, this caused an annoying problem where the text would become pixelated, so this approach was just not viable. I had to find a different way... I tried another approach where I would dynamically scale the font size (multiplying the base font by the zoom scale). This did indeed solve the problem for normal text objects, so the text no longer became pixelated. But this approach introduced it’s own problem for note objects which also use `Text()` from SwiftUI. It caused layout jumping. If I understand it correctly, the rendering engine called `CoreText` does not scale fonts linearly. For example, a 14pt font scaled to 200% doesn't always take up exactly twice the space of a 7pt font. I noticed that small adjustments to letter-spacing would happen at every fractional font size. This meant that as the user zooms, a word that barely fit on line one suddenly becomes 0.1 pixels too wide and wraps to line two. This would cause the entire text block to "jump" vertically as lines snap back and forth between different wrap points. It felt broken and looked bad. The solution ended up being to use AppKit. Unlike SwiftUI’s `Text()`, the `NSTextView` from #AppKit allows us to manipulate the text to a much greater degree. So this did indeed fix the problem for note objects. BUT... It also introduced a problem since I use `ImageRenderer` to take a snapshot of the canvas, which is then used to display a preview of the canvas in search results. The thing is that `ImageRenderer` **ONLY** works with SwiftUI. It is simply incapable of capturing an AppKit view like `NSViewRepresentable`. It seems like my only choice is to render an `NSTextView` in the canvas, and then render SwiftUI `Text()` when the app needs to take a snapshot with the `ImageRenderer` API. #dev
I hear a lot of developers complain about AI having sucked all the fun out of programming. I can understand that if what they enjoy is writing the code. But personally I feel the complete opposite. I have never enjoyed programming more than now. I’m so happy that I no longer need to be spending a ton of time on writing the code. Instead, I can use that time on figuring out the best architecture for the system I’m building. When I work with an LLM, I write Markdown documents that I use to prompt it with detailed descriptions of the features I want the LLM to implement. This could include which APIs I expect it to use. If it’s a rather unknown API, then I copy-paste the definition of it so it knows exactly which parameters it expects and which methods can be called. I find that if I don’t give the LLM some clear directions, then it will start using deprecated methods and start writing new helper methods even though it could just use existing ones. In general, I think LLMs can be a bit lazy at times… But they can do excellent work if given a good prompt. #AI #LLM #coding
Here's a look at V1 of the grid view mode in fullscreen search: Most of my time has been working on a snapshot feature where the app takes a snapshot of the canvas. This will then be used in the fullscreen search to show a preview of the canvas file. I will most likely use the same approach for note documents instead of rendering the actual note document. It's less resource intensive to load an image compared to a note document with multiple blocks. Later, I will also use the snapshot feature in preview overlays that appear when hovering a note or canvas file. But before this, I think I should fix the UI for fullscreen search first. Just simple things like the list of search result should extend as much height as possible meaning all the way to the bottom of window. In the video, you can see the canvas being a bit laggy in the beginning when it's first loading. I do have some debouncing, but I think it might be a good idea to have a more sophisticated approach where only certain objects are loading at the same time. Perhaps only the ones inside the viewport, but if too many are showing, then have a maximum of how many can load at the same time. #dev #AppKit #Swift #SwiftUI #macOS View quoted note →
I have been working on improving the fullscreen search functionality by implementing a preview mode. So now, there are two modes: _Grid_ and _Preview_. The preview mode is simply the grid but with a preview of the file’s contents. There will also by a third mode called _List_. I experienced some serious performance issues after implementing this new preview mode. This is really what most of my time was spent trying to fix. Before I was showing a real representation of the canvas where I rendered all the objects and scaled them to an appropriate size. Doing a full render of a canvas that could potentially contain 20 different objects (images, videos, note documents, etc.) is a bad idea. And it becomes an even worse idea if the search results include more than one canvas. I decided to figure out a different solution that would solve the performance issues. So I started experimenting… I figured that I could actually just show an image of the canvas, so I implemented functionality to take a snapshot of the canvas whenever closing a canvas file or switching to a different tab. This works really well and I think I will choose the same approach for the minimap inside canvas files. Because currently, I am doing a full render of the objects inside a minimap and there is just no reason to do that… Instead I will just use the snapshot approach. This means, I should also save a snapshot after making a change in the canvas, but of course with a debounce timer. For example, if moving an object, then take a new snapshot after a timer has expired. Will share a video demonstrating of the fullscreen search later…
I love blur effects and because of this, one of the first things I tried when I started learning AppKit, was how to implement a beautiful blurry background to elements. This is something that's super easy in HTML and CSS, and while it's also quite easy in AppKit, you don't get anywhere near as much customization - something as basic as blur radius cannot be changed in the `NSVisualEffectView` API provided by Apple. I always found this super annoying so I began looking for hacks to workaround this problem. Unfortunately, I never got very far with it... AppKit is complex and a lot of it is poorly documented, so for a beginner, it's a big and hardcore task. And therefore, I just decided to live with the default `NSVisualEffect`... But just now, I found this repository: This repository seems to have found a workaround to the _custom blur radius_ issue. However, they work in UIKit and I work in AppKit, so it's not quite the same, but should still be very useful as I think they use `CAFilter` which I believe works in both AppKit and UIKit. I've never worked with `CAFilter` before, so should be interesting. Apparently it's a super old thing, and also, it's a private API so I don't expect to be able to find any official documentation about it... #dev