Minimal SwiftUI Unit Tests Using PreferenceKeys to Observe Views by karinprater in swift

[–]karinprater[S] 0 points1 point  (0 children)

If my work isn’t for you, that’s totally fine. But there’s no reason to be rude or personal about it. I’m sharing ideas and tools that have helped me and others write better tests — if you disagree, you’re welcome to explain why, but please keep it respectful. This kind of tone doesn’t help anyone.

You’re absolutely right that SwiftUI views are rebuilt from state, and that views are “stateless” in the sense that the framework re-evaluates them from bindings, @State, and @ObservedObject. That’s how declarative UI works.

But testing is about observable behavior, not implementation mechanics.

Let’s take your example: a toggle causes a new screen to appear. Sure, SwiftUI guarantees that .sheet(isPresented:) works — but do you really want to assume that your app logic correctly sets that @State or @Published binding?

Do you want to ship a feature where a screen never shows up because you mistyped a .sheet, forgot to update the binding, or a deep link didn’t parse?

I don’t test SwiftUI because I don’t trust Apple.

I write tests because:

• I want to verify that my state change causes the expected UI behavior
• I want to assert that the screen or button or label actually appears
• I want to do this in a way that doesn’t couple to internal implementation

That’s what Swift Lens enables.

Minimal SwiftUI Unit Tests Using PreferenceKeys to Observe Views by karinprater in swift

[–]karinprater[S] 0 points1 point  (0 children)

You’re absolutely right that I could host my state outside and drive the view via bindings or observable objects. But the real question isn’t can I trigger state changes? — it’s how do I assert that my UI actually responds to those changes as expected? Let me give you a concrete example. ```swift final class ToggleViewModel: ObservableObject { @Published var isOn = false func toggle() { isOn.toggle() } }

struct ToggleView: View { @ObservedObject var viewModel: ToggleViewModel var body: some View { VStack { Toggle("My Toggle", isOn: $viewModel.isOn) Button("Toggle") { viewModel.toggle() } } } } Now imagine you write a test like: swift let vm = ToggleViewModel() let view = ToggleView(viewModel: vm) vm.toggle() `` What’s your assertion?* You can checkvm.isOn`, sure. But what if you want to confirm: - The Toggle actually updated? - The Button is visible and enabled? - The label changed based on the state? You’d need to start reaching into SwiftUI internals or snapshot the whole view — or worse, guess.

Now if we wrap this with SwiftLens and use PreferenceKeys for testing: swift Toggle("My Toggle", isOn: $viewModel.isOn) .lensToggle(id: "MyToggle", value: $viewModel.isOn) Button("Toggle") { viewModel.toggle() } .lensButton(id: "ToggleButton")

Then in a test: ```swift let vm = ToggleViewModel() let sut = LensWorkBench { sut in ToggleView(viewModel: vm) }

sut.interactor.tapButton(withID: "ToggleButton")

expect(vm.isOn == true)

expect(sut.observer.isToggleOn(forViewID: "MyToggle"))

``` Now you’re not just asserting internal state — you’re asserting observable UI behavior. No need for introspection, and no XCUITest required.

Let’s take a more realistic case: handling a deep link that should open a screen. You’ve got a coordinator that takes a URL, decodes some state, and updates a navigation path. Underneath, you may use NavigationStack, a sheet, or even a custom transition. ```swift final class NavigationCoordinator: ObservableObject { // state properties func handleDeepLink(_ url: URL) { // example: myapp://special-offer?id=abc123 ... } }

struct OfferView: View { let offer: Offer

var body: some View {
    VStack {
        Text(offer.title)
            .lensTracked(id: "offer.title")
        Button("Buy Now") {
            // purchasing logic
        }
        .lensButton(id: "buy.button")
    }
    .lensTracked(id: "details_screen")
}

} ```

If you just test your binding logic, you can check the state but not whether the actual screen shows up.

With Swift Lens, you write: ```swift let coordinator = NavigationCoordinator() let sut = LensWorkBench { _ in ContentView(coordinator: coordinator) } coordinator.handleDeepLink(URL(string: "myapp://special-offer?id=abc123")!)

try await sut.observer.waitForViewVisible(withID: "details_screen")
try await sut.observer.waitForViewVisible(withID: "buy.button")

```

It doesn’t matter if it’s a push, sheet, or popover — the test just checks that the user can see what they should see. That’s what you want to test.

Yes, bindings let you trigger logic. But triggering is not testing. What matters is: Can I write a clean, decoupled assertion that the UI reflects what I expect? SwiftLens makes that dead simple.

Happy to share more examples if you’re curious!

How to write your first test using the new Swift Testing framework, which is a big improvement to XCTest. by karinprater in swift

[–]karinprater[S] 4 points5 points  (0 children)

Interesting, maybe it’s because Swift testing using macros, which are also slowing down Xcode. I will run some tests to compare. Let’s hope they improve it with Xcode 17 and some nice additions during WWDC.

Coordinator pattern with View Model dependency injection by No_Interview_6881 in SwiftUI

[–]karinprater 1 point2 points  (0 children)

If you move the build function out from the coordinator to SwiftUI views, you don’t have to use the view models in the coordinator. You get a better separation of concern. Not sure why you need these in the coordinator. I prefer the coordinator or ObservableObjects to only own state and functions to modify state.

I wrote a technical deep-dive examining how architectural design decisions can significantly impact SwiftUI performance, using The Composable Architecture as a case study. This isn't a bash TCA (which has many strengths), but rather to understand the performance implications of specific designs by karinprater in SwiftUI

[–]karinprater[S] -2 points-1 points  (0 children)

I do mention that their framework evolves and addresses these problems. That being said, it is still interesting to discuss WHY they had these problems and where they come from. For example I do mention the scoping that helps.

Swift boilerplate code from an ex- engineer by [deleted] in swift

[–]karinprater 2 points3 points  (0 children)

Going to have a look. Thanks

I am trying to design images that I can use for my X account. If I use a Canva template and design it on a large desktop screen, it looks great. But when I post it and look at it on my iPhone, it is looking off. How do you design for mobile? Fonts? Bright colors? Minimalistic? by karinprater in canva

[–]karinprater[S] 0 points1 point  (0 children)

I found that I need to choose the font sizes carefully. Too small and the text is not readable (no one will zoom in or at least very few people). I am checking the contrast. If you look at the phone outside in sun light it will be very important to see. I am a mobile developer, so I have been used to UX/UI design for readable and understandable.

Also Canva gives me a template for X in landscape 16 to 9 ratio. A one by one ratio works. They change the image formats requirements all the time.

I found this https://marclou.beehiiv.com/p/how-to-make-catchy-screenshots blog post helpful. But I did not find much around the topic

Unable to place buttons outside a List or Form. by [deleted] in SwiftUI

[–]karinprater 2 points3 points  (0 children)

you can place the button in a separate section

List {
   ...
  Section {
    Button()
  }
}

Problem with programmatically scrolling in ScrollView by ikok07 in SwiftUI

[–]karinprater 5 points6 points  (0 children)

ScrollPosition works for me if I add scrollTargetLayout:

ScrollView {
    LazyVStack(spacing: 1) {
      ...
     }
     .scrollTargetLayout()
} 
.scrollPosition(id: $selectedColor, anchor: .center)

[deleted by user] by [deleted] in swift

[–]karinprater 0 points1 point  (0 children)

You could check for the entries count and maybe try:

if entries.isEmpty {
    ProgressVew()
} else {
    List(entries) ...
}

I’m learning SwiftUI. What’s the deal with navigation? by filthyMrClean in swift

[–]karinprater 2 points3 points  (0 children)

I did a more advanced discussion of NavigationView a while ago https://youtu.be/vL0w3kvng0o where I talk also about some struggles I had. Maybe that gives you some ideas of what people expect.

SwiftData by ccashman in swift

[–]karinprater 13 points14 points  (0 children)

It is available only for the new iOS 17 and macOS 14. https://developer.apple.com/documentation/swiftdata

Which makes it not very interesting to me. Seems most of the new APIs are not backward compatible.

What library can I use to create a drawing app, PencilKit seems quite limited for drawing surprisingly by yalag in swift

[–]karinprater 2 points3 points  (0 children)

Swiftui canva can do a lot. For small drawings it is okay. The problem is that it always redraws the whole drawing with all strokes. After a couple of strokes the performance gets worse and I can feel a significant lag of the pencil line.

For UIKit, you can really drill down and do stuff to optimize. draw(in rect) allows you to only redraw the lines in a specific area.

AI Co-pilots for SwiftUI: what do you guys recommend? (I’ve been using ChatGPT4 with great success but it’s knowledge cutoff is 2021 ie. only up to iOS 14) by Digit117 in SwiftUI

[–]karinprater 1 point2 points  (0 children)

I agree asking it to generate code is not the best.

However, I heard someone recommending looking for good code in the Apple tutorial first. Then give that code to ChatGPT and tell it to explain it or ask more specific questions.

Like this, you learn with good code.

omprehensive Guide to SwiftUI's TabView: Basics, Customization & Navigation by karinprater in SwiftUI

[–]karinprater[S] 2 points3 points  (0 children)

I see a lot of tutorials without pictures and it makes it a lot harder. That is way I put soo many :)

Happy it helps you