Lukas Z's Blog

Link: Wikitok

Found this TikTok clone today: wikitok.vercel.app.

WikiTok

What a fun idea. While scrolling Instagram and TikTok is rumored to make us more stupid, this could actually make us more intelligent.

Using a VPN Service Isn’t Fun

I’ve subscribed to a VPN service and it’s really not fun to use them. The problem is that Google, Youtube, and other big sites, detect it and then they spam you with Captchas. Dubious security is exchanged for obvious inconvenience.

A much better experience for me was running Algo. It sets up a VPN on a Digital Ocean server (other options are included, but I use D.O.), and then provides a simple way to set it up on multiple devices using Wireguard config files and qr codes.

The VPN services have the advantage that switching is faster and easier. With Algo I would have to set up my own VPN server each time. But all things considered it’s the better option for me.

A Map of Germany

Since I had fun with it yersterday, I decided to create one more tiny web app. This time it’s a map of Germany with some landmarks. I believe it’s useful to learn where things roughly are.

A Map of Germany

This time the LLM did 100% of the coding. I did not have to correct anything manually and the whole process to get the result you see here took about 25 minutes.

Click here to open the map

(It probably won’t work well on mobile.)

Star Wars Quiz

I played around with Google Gemini and chatGPT and wanted to see if I can briefly create a small fun web app.

I could, but when something did not work, it took some light manual tweaking and debugging to tell the LLM exactly what the problem was. But it did about 90% of all the work and the whole thing took about 30 minutes to finish.

Click here to play it

And may the Force be with you.

A Weird Little Trick When Xcode Canvas Preview Is Stuck

When our SwiftUI app got bigger, the Canvas previews took more and more time to build, until the feature was practically no longer usable. But sometimes I still wanted to wait.

Well, as weird as it sounds, here’s a trick to make it appear faster:

Just go up to the “Product” menu and hover your mouse over the items. When you highlight “Scheme” the Canvas will get unstuck and display immediately.

Make Xcode Canvas great again

At least this works for me in Xcode 15.0.0.

¯\(ツ)

How to Redirect HTTP and HTTPS Requests Using Namecheap and AWS

This is a mini writeup for a very specific case.

Let’s say we have a domain name, ourcooldoma.in. And for some reason we want to redirect all traffic to somecool.org. And the following things also apply:

  • ourcooldoma.in is registered at a hoster like Namecheap.
  • somecool.org does not belong ot us at all.
  • we want to redirect ourcooldoma.in and www.ourcooldoma.in.
  • we are already using AWS.

(Told you it was specific..)

Then we can do this:

  1. Set up an empty S3 bucket at AWS and make it public.
  2. In the properties of the bucket use the Static Website Hosting feature in Properties and define a redirect to somecool.org. Take note of the Endpoint value in the Static Website Hosting box.
  3. Because we support HTTPS use ACM to create a certificate that matches ourcooldoma.in and www.ourcooldoma.in (or *.ourcooldoma.in).
  4. Create a Cloudfront distribution that uses that bucket’s Endpoint as origin and is valid for the domains www.ourcooldoma.in and ourcooldoma.in. Use the certificate from the step before.
  5. Configure ALIAS records at Namecheap for www and @ pointing to the domain name of our Cloudfront distribution.

Done!

Now every request to ourcooldoma.in will result in a DNS query that returns the ALIAS entry (though I believe ALIAS is not a standard key but a technicality provided by Namecheap and others to circumvent the restriction of CNAME, which one would normally use, but which cannot be used with @ unless you are ok with masking your MX and other subdomain records).

So our request will be actually fired against the IP address at Cloudfront but the client still expects a valid SSL certificate for ourcooldoma.in. Cloudfront provides that (created in step 3) and we can finally get to the (cached) bucket, which only does one thing: 301 redirect the client to somecool.org.

How to Include Binary Vendor Frameworks in Xcode With and Without Cocoapods

Sometimes we have a closed source binary library that we want to include in our iOS project. To do that Xcode needs to do the following things:

  • Find the framework (via defaults or the Framework Search Paths setting).
  • Embed the framework in the bundle and sign it.
  • Link against the framework during build.
  • Strip any architectures from the framework binary that are not required (or even forbidden: You cannot for instance ship a binary that contains code for x86_64 through AppStoreConnect).

I want to add another complication, which is of course optional: The framework is only included for some configurations, for example DebugWithFramework. It should not be included in all other configs.

First I will show how to do it manually and then I will show the Cocoapods way, which, in my humble developer opinion, is superior.

Approach 1: The manual approach

So let’s say we want to do the above steps manually.

For the optional “only some configurations” requirement make sure the configurations have been created.

Step 1: Add the Framework

We can just pull the binary framework into our file tree in Xcode and we’re done. However, if we only want to include it in some configurations we should uncheck the target membership of the framework and add it manually.

Step 2: Framework Search Paths & Linker Setting

We need to tell our build system about our Framework. I believe this is already done for us if we add the Framework to our target and/or if we drag the Framework into the “Embedded Frameworks” box in settings.

But this part of the post is about the manual approach, with inclusion for only certain configurations, so we need to do two things: Set the Framework Search Path and tell the Linker.

But this is easy:

In Build Settings look for Other Linker Flags and add two lines for the configurations that matter (you may need to click the drop down arrow first): “-framework” and “CoolFramework” if “CoolFramework” is the name of your framework, otherwise please substitute the correct value.

Finally in Build Settings look for Framework Search Paths. Again select the configurations that you care about and add a new line below $(inherited) that looks something like this:


$(PROJECT_DIR)/Frameworks/MyCoolFramework/debug

And you should be good to go.

At this point your app probably compiles but at runtime it crashes complaining that it can’t find the Framework.

That’s because it’s not in the Bundle yet. For that you need the next step:

Step 3: Embed & Sign

If it’s supposed to be included for all configs we can just add it by dragging it into the Embedded Frameworks box in our project settings (assuming it’s not already there). Then make sure the option is set to “Embed & Sign” and you’re done.

It gets slightly more complicated if we include it manually. For that we can add a custom script build phase in our Build Phases tab that looks somewhat like this:


if [[ -z ${SCRIPT_INPUT_FILE_0} || -z ${SCRIPT_OUTPUT_FILE_0} ]]; then
    echo "This Xcode Run Script build phase must be configured with Input & Output Files"
    exit 1
fi

echo "Embed ${SCRIPT_INPUT_FILE_0}"
if [[ $CONFIGURATION == 'DebugWithFramework' || $CONFIGURATION == 'ReleaseWithFramework' ]]; then
      FRAMEWORK_SOURCE=${SCRIPT_INPUT_FILE_0}
        FRAMEWORK_DESTINATION=${SCRIPT_OUTPUT_FILE_0}
        DESTINATION_FOLDER=`dirname ${FRAMEWORK_DESTINATION}`
        
        mkdir -p ${DESTINATION_FOLDER}
        cp -Rv ${FRAMEWORK_SOURCE} ${FRAMEWORK_DESTINATION}

        CODE_SIGN_IDENTITY_FOR_ITEMS="${EXPANDED_CODE_SIGN_IDENTITY_NAME}"
        if [ "${CODE_SIGN_IDENTITY_FOR_ITEMS}" = "" ] ; then
            CODE_SIGN_IDENTITY_FOR_ITEMS="${CODE_SIGN_IDENTITY}"
        fi

        BINARY_NAME=`basename ${FRAMEWORK_DESTINATION} .framework`
        codesign --force --verbose --sign "${CODE_SIGN_IDENTITY_FOR_ITEMS}" ${FRAMEWORK_DESTINATION}/${BINARY_NAME}
        echo " ✅ Embedded successfully"
    else
        echo " ❌ Non MyCoolFramework build detected - do not embed"
    fi

Script phases have input and output files (extra input text fields), and in our case we can set it to the following values:


$(SOURCE_ROOT)/Frameworks/MyCoolFramework/debug/MyCoolFramework.framework

for the Input and


${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MyCoolFramework.framework

for the output.

So the output specifies where the binary goes in the compiled bundle.

Disclaimer: This code is a slightly modified snippet from Github. If I find it, I’ll link to it later.

Step 4: Strip Unnecessary Architectures

The app may run fine now in your Simulator or even on an actual iPhone or iPad, but if we want to submit our app to the AppStore we need to do one more thing: Remove architectures that are not used by the target devices.

Quick sidenote: What are architectures?

Well if you write source code it eventually gets compiled into machine code. Zeroes and Ones that are actually instructions (opcodes + arguments) for the CPU. But CPUs are different, they have different architectures and each architecture has different opcodes. (Simply speaking.) Until Apple switches to Apple Silicon, your mac (and your iOS Simulator) most likely has a x86_64 Intel CPU. And your iPhone most likely has a armv7 CPU.

If you have a binary framework that you did not compile yourself it must have the binary code for correct architectures already included. And the best case is that it has the code for _all_required architectures.

So what do we do before uploading to the AppStore?

We get rid off all binary code that is not for iPhone and iPad.

And we do it using a command line tool called lipo.

Now remember, Xcode does all this for you automatically, usually, but we are handling a custom case here where our framwork is binary.

Here’s the build script phase for that:


if [[ $CONFIGURATION == 'DebugWithFramework' || $CONFIGURATION == 'ReleaseWithFramework' ]]; then

  FRAMEWORK="MyCoolFramework"
  FRAMEWORK_EXECUTABLE_PATH="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/$FRAMEWORK.framework/$FRAMEWORK"
  EXTRACTED_ARCHS=()

  for ARCH in $ARCHS
  do
    lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
    EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
  done

  lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
  
  rm "${EXTRACTED_ARCHS[@]}"
  rm "$FRAMEWORK_EXECUTABLE_PATH"
  mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"
fi

This can be changed a bit to work for all frameworks I guess, but here it’s the special case in which we have one framework, MyCoolFramework, and two configurations DebugWithFramework and ReleaseWithFramework.

Step 5: Using the Framework in our code

Just briefly I want to mention how you can prevent compilation errors by using an #if clause in your code. Consider this Swift code:


#if canImport(MyCoolFramework)
import MyCoolFramework
#endif

You can use these if-blocks liberally everywhere in your Swift code.

Conclusion & Open Questions

I think we’re done. (If not please write a comment so I can fix it. I don’t have a lot of time to double check everything I’ve written here. Thanks.)

As you can see the manual approach is tiresome but at least we learn somehting about how Xcode builds apps for us!

I want to mention one more complication that can and probably will occur: What if our Framework has dependencies, for example Cocapods that must be present? They can be added in the Podfile but if we need to restrict the inclusion to certain configurations then we must take extra steps.

That’s why we should use Cocoapods all the way in the first place! ;)

Ok let’s look at how that’s done in the second part.

Approach 2: Using Cocapods

Now we will make our own Cocoapod that simply includes our vendor framework. Now why do we want to do that? Because Cocoapods takes care of all the steps above for us!

That’s right, the batteries are included. Let’s start.

Btw. I assume you are already using Cocoapods and your project already has a Podfile. (If not, please add Cocoapods now and run pod init in your project folder.)

Step 1: Writing a gemspec file

A pod is defined by a gemspec file that instructs the pod binary what to do when installing. I’ll keep it brief and just paste you own for our case. But there are more options which may be required. Especially if you are also having swift and objective c files in there somewhere that must be compiled. Our case is actually simpler because the framework is binary.

We’re writing a local pod by the way. You could have it remotely in some git repo and you could even publish it in the cococapods repository but our case here it’s local only and it’s in the same git repository as our app. It’s basically just in a subfolder.

Here we go:


Pod::Spec.new do |spec|
  spec.name         = "MyCoolFramework"
  spec.version      = "0.0.1"
  spec.summary      = "Podspec wrapper for the MyCoolFramework framework."
  spec.authors      = { "Lukas Zielinski" => "http://www.lukaszielinski.de" }
  spec.homepage     = "http://www.lukaszielinski.de"
  spec.platform     = :ios, "12.0"
  spec.source       = { :http => 'file://' + __dir__ + '/MyCoolFramework.framework.zip' }
  spec.public_header_files = "MyCoolFramework.framework/Headers/*.h"
  spec.ios.vendored_frameworks = "MyCoolFramework.framework"

  spec.dependency 'RxSwift', '5.1.0'
  spec.dependency 'RxCocoa', '5.1.0'
  spec.dependency 'RxDataSources', '4.0.1'
end

The gemspec should be in the same folder as they framework and the framework folder should be zipped. Cocoapods will unzip the archive during installation.

There’s also a speciality here: Our framework has dependencies, it requires the RxSwift pod to run. I’ve added this to show you to demonstrate that it’s easy to add dependencies now and also to show a peculiarity about Cocoapods below.

Step 2: Adding our brand new pod

Let’s see how our Podfile looks now:


target 'App' do
  pod "MyCoolFramework", :podspec => "Frameworks/MyCoolFramework/debug/", :configurations => ["DebugWithFramework, "ReleaseWithFramework"]

  pod "RxCocoa", "5.1.0", :configurations => ["DebugWithFramework, "ReleaseWithFramework"]
  pod "RxDataSources", "4.0.1", :configurations => ["DebugWithFramework, "ReleaseWithFramework"]
  pod "RxSwift", "5.1.0", :configurations => ["DebugWithFramework, "ReleaseWithFramework"]
  # explicit dependency for RxCocoa:
  pod 'RxRelay', '~> 5.0', :configurations => ["DebugWithFramework, "ReleaseWithFramework"]
end

And if we don’t care about configurations it’s just this:


target 'App' do
  pod "MyCoolFramework", :podspec => "Frameworks/MyCoolFramework/debug/"
end

Now we can run pod install and we’re good to go! We can run the app and even submit it to the AppStore, because Cocoapods takes care of all the steps from the manual approach for us!

There’s one thing that you should look out for regarding the configurations. As you see in the Podfile examples, it gets much more verbose when we want to restrict the pod to be included in certain configurations only. That is because Cocoapods requires us to also specify all the dependencies and the dependencies’ dependencies (transitive dependencies) explicitely IF we want to restrict them to some configurations.

Consider this:


target 'App' do
  pod "MyCoolFramework", :podspec => "Frameworks/MyCoolFramework/debug/", :configurations => ["DebugWithFramework, "ReleaseWithFramework"]
end

Here our framework is not included in the “Debug” stage, however it’s dependencies are. This is something to look out for.

Protip: Build and afterwards look inside your compiled app’s package. There’s a frameworks folder there. You should only see pods that are dependencies of our framework in apps build with those configurations.

Final words

That’s it, I hope this helps someone. Please send me any corrections or comment below. Thank you!

Everyone Misunderstands the Mazda MX-5 Luggage Rack

I’m a big fan of the Mazda MX-5, which is also called Mazda Miata. I had one a few years ago and I still miss it. I think this is the perfect car. Unless you need a third seat or luggage space..

Speaking of luggage. What fascinates me is that virtually everyone installs the luggage rack the wrong way!

The only exception is the third gen Miata where it’s usually done right, and despite the fact that we all see the 3rd gen on the road most of us still don’t get it.

Here is what I mean:

Miata NB

The rack on this beautfiul 2nd gen should be rotated by 180 degrees. The short sticking out part should be towards the front, not back.

Why?

Because its function is to prevent your luggage from chopping your head off if you crash into something. It’s not there to prevent your luggage from falling down because of the crazy acceleration of the Miata.

Because, let’s face it, the Miata doesn’t accelerate that quickly.

3rd gen (“NC”) owners get it usually right, because the luggage racks are shipped with a brake light, since the rack obscures the 3rd brake lamp. So there’s just one way to install it, the correct way, the way that prevents your bags from killing you.

Or not just you. Everyone else that is unlucky enough to be crashed into by a Miata!

Just saying.

(Fanboy disclaimer: Luckily, this is a mostly theoretical discussion. The Miata handles awesomly. It’s rare that you would loose control and crash.)

But still.. I wonder, what else in life is obviously wrong, that we, the average people who never thought too much about it, don’t get?

CASIO W96-H: How to Turn the Hourly Beep on and Off

Here’s a quick guide on how to turn that hourly beep off with Casio W96-H.

casio w96h buttons

Guide:

  1. In normal time mode press button 1 once to get into alarm mode (“AL” in the upper left).
  2. Press button 2 once to get into the hourly time signal screen (“:00” displayed).
  3. Press button 3 to toggle the hourly signal on and off.

Anyway, here’s a link to the manual.