Luny
Back

Dictionary

Originally a web app, but I deemed it too simple to be one, and utilized this opportunity to learn ObjectiveC and UIKit.

Published on
Updated on

3.8

objc
Dictionary

Overview

This was a intermediate level Frontend Mentor projects, primarily targeted at web developers. But I thought it would be too simple and trivial for me to implement it on the frontend, I decided to make it a challenge to re-implement it, including themes, and animations within an iOS 18 App.

But that’s not it! I will also not be leveraging modern features or modern patterns like Swift and SwiftUI (similar to Stateful React), but I will go the ancient way: pure ObjectiveC and UIKit.

Features

Loading screen

No word screen

Dark mode

The Experience

Using OBJC to do this project, which at first I thought it would be quite small since the application only consists of one screen, but it started spiraling out as more and more components require inheritance from one of many UIKit’s components.

All in all, I learned a lot of new things and it was relatively fun trying to do things in ObjC, which also surprises me that Apple still supports ObjC up into iOS 18. If any, my next project would be in Swift, or even using SwiftUI because this was not that fun to setup.

Layout Anchors

    // Create the main view to put in the middle of this view.
    UIView *mainView = [[UIView alloc] init];
    mainView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:mainView];
    [NSLayoutConstraint activateConstraints:@[
        [mainView.centerXAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.centerXAnchor],
        [mainView.centerYAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.centerYAnchor],
        [mainView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor],
        [mainView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor],
    ]];

Anchors are a way for the app to have flexible layout, while still knowing exactly where to put each component of the UI tree. You can imagine anchors like glues, they stick each edge of the component to another edge of another component, and once one resizes or moves, the other resizes or moves together.

Android also uses the same mechanisms, but with XML directives like width=container, I think.

Changing entrypoints

All UIKit apps start with a single Entrypoint storyboard, which is somewhat hard-coded in XCode. You need to remove all references to the Entrypoint storyboard, from Project Settings to any code-related things.

- (void)scene:(UIScene *)scene
    willConnectToSession:(UISceneSession *)session
                 options:(UISceneConnectionOptions *)connectionOptions {
    if (![scene isKindOfClass:UIWindowScene.class]) {
        return;
    }

    // Create a UIWindow using the scene if it is a WindowScene.
    UIWindowScene *windowScene = (UIWindowScene *)scene;
    UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene];
    self.window = window;

    // Setup the root view to be the navigation controller, and open up the main view controller.
    DTMainViewController *rootController = [[DTMainViewController alloc] init];
    UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:rootController];
    self.window.rootViewController = navController;
    [self.window makeKeyAndVisible];
}

After that, you can create a new UIWindow where the starting scene is the scene that gets passed into SceneDelegate. You can now invoke your entrypoint view controller and assign that the “root view”.

I additionally used a UINavigationController here to support navigations like back, forwards, easily implementable by NavigationStackView in SwiftUI.

Async IO

Some of you are probably very well-versed in Asynchronous IO with certain languages like Golang with native goroutines, or JavaScript with async-await, or even Python got there async-await now, or even JAVA got Promises-like structures with CompletableFutures.

ObjectiveC has NO SUCH THING. Everything asynchronous has to be done through passing callbacks, which also surprised me that ObjectiveC supported callbacks with a stronger reflections support than generic function pointers.

- (void)searchDidPress:(NSString *)query {
    // First, we put up the "Loading" state.
    [self setupChildController:[[DTLoadingViewController alloc] init]];

    // Then we send a request asynchronously, and handle later.
    NSString *urlString = [@"https://api.dictionaryapi.dev/api/v2/entries/en/"
        stringByAppendingString:
            [query stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLPathAllowedCharacterSet]];

    // Create a URL request to handle it.
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]
                                                                cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                            timeoutInterval:5];
    request.HTTPMethod = @"GET";

    NSURLSessionDataTask *task =
        [NSURLSession.sharedSession dataTaskWithRequest:request
                                      completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                        // Handle UI changes on the main queue.
                                        dispatch_async(dispatch_get_main_queue(), ^{
                                          [self handleData:data response:response error:error];
                                        });
                                      }];
    [task resume];
}

All in All

To look back onto this, I was very proud of being able to write up ObjectiveC in a few days, as the web project would have taken me less than a day. It was pretty fun to use pure UIKit systems that you can’t directly preview like SwiftUI, and composing even a simple “flexbox”-like takes up 24 lines of just configurations and assignments.

    UIStackView *mainStackView = [[UIStackView alloc] init];
    mainStackView.axis = UILayoutConstraintAxisVertical;
    mainStackView.spacing = 32;
    mainStackView.alignment = UIStackViewAlignmentFill;
    mainStackView.translatesAutoresizingMaskIntoConstraints = NO;
    [stackView addArrangedSubview:mainStackView];

Can you GUESS what it does? It’s THIS.

<div class="flex flex-col items-stretch gap-8"></div>

After this, I could say that even given a legacy iOS codebase, I think I could pull through with something.