Masking out text in SwiftUI

Today’s post is rather short: Let’s mask out text on a view in order to make it transparent using SwiftUI.

First, an extension on View to generate an inverted mask:

extension View {
    public func inverseMask<M: View>(_ mask: M, cornerRadius: CGFloat) -> some View {
        let inverseMask = mask
            .background(Color.white)
            .compositingGroup()
            .luminanceToAlpha()
            .cornerRadius(cornerRadius)
        return self.mask(inverseMask)
    }
}

Then the view itself. Just a white Rectangle where we apply our new mask:

struct CutOutText: View {
    let text: String

    var body: some View {
        Rectangle().fill(Color.white).padding()
            .inverseMask(
                Text(text)
                    .font(Font.custom("Avenir Next", size: 14).weight(.medium))
                    .padding(.horizontal, 10).padding(.vertical, 5), cornerRadius: 5

            )
    }
}

And finally, let’s test it on a LazyVGrid!

struct ContentView: View {
    var body: some View {

        let colors = [Color.red, Color.yellow, Color.blue, Color.green, Color.orange, Color.black, Color.pink, Color.purple]

        let columns = [
            GridItem(.flexible()),
            GridItem(.flexible()),
        ]
        return
            ScrollView {
                LazyVGrid(columns: columns) {
                    ForEach(colors, id: \.self) { color in
                        ZStack {
                            Rectangle().fill(color).padding().frame(height: 200)
                            CutOutText(text: "This is a demo")
                        }
                    }
                }

            }

    }
}

LazyVGrid with cut out text

This approach could be applied to images, views, etc.

Thanks for reading! ✌️

Build and deploy a Jekyll site using GitHub Actions

One thing I really enjoy about my current company is how we leverage the power of GitHub actions to run many different tasks in our workflows: from linting and formatting, to creating releases or deploy apps to App Store Connect.

Is quite impressive the amount of actions that the community put up together in the GitHub Marketplace.

I wanted to therefore leverage this technology in a smaller scale: the build and deployment of this Jekyll blog.

The idea would be that on every new push to the master branch, Jekyll should build the site and upload it to my hosting site via FTP. This can achieved surprisingly easily using the Build and the FTP Deploy actions.

The resulting workflow is quite straightforward:

on:
  push:
    branches:
      - master
jobs:
  build:
    name: Build & Deploy
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Build
        uses: jerryjvl/jekyll-build-action@v1

      - name: Deploy
        uses: SamKirkland/FTP-Deploy-Action@2.0.0
        env:
          FTP_SERVER: $
          FTP_USERNAME: $
          FTP_PASSWORD: $
          LOCAL_DIR: _site
          REMOTE_DIR: moraleda.info
          ARGS: --delete --transfer-all
          # --delete arg will delete files on the server if you've deleted them in git

It is important to notice that, since jobs run in isolation, both steps should run in the same job in order to share the environment, otherwise we would need to pass the output of build to the deploy step.

Bonus track

I also added a different workflow to run on pull requests in order to check the spelling of Markdown files (which contain most of the blog content):

name: Check Spelling
on:
  pull_request:
    paths:
      - "**.js"
      - "**.vue"
      - "**.txt"
      - "**.html"
      - "**.md"
jobs:
  spelling:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          ref: $
      - name: Check Spelling
        uses: UnicornGlobal/spellcheck-github-actions@master

Both workflows need to live under the .github/workflows folder, so that GitHub will know about them.

Thanks for reading! ✌️

iOS - Localizing strings in a project

Localization is an often overlooked problem. We start a project, we know that we only have to support one language and we approach our string-handling based on that requirement. When the time comes to support a second language, the project grew so much that adapting to that second language causes pain and frustration.

Doing it right from the beginning

iOS and Xcode support localized strings in a rudimentary-but-pragmatic way: we can define a Localizable.strings file, and localize that file in n languages. For each language we will have a key-value file that will contain all our strings:

// German strings

"ok_button_title" = "OK";
"cancel_button_title" = "Abbrechen";

/*Main Screen*/
"main_title" = "Willkommen";
// Spanish strings

"ok_button_title" = "Vale";
"cancel_button_title" = "Cancelar";

/*Main Screen*/
"main_title" = "Bienvenido";

For accessing these strings all we need to do is to use NSLocalizedString(key, comment: "") and pass the key we are interested in. Eg. NSLocalizedString("ok_button_title", comment: "")

This is going to work nicely and, if we were to launch the app in German or Spanish, the system will know which strings to use.

Getting they keys under control

The main drawback of that approach is that keys are strings and if they start going around, that could be prone to error.

This can be easily solved as well. Let’s define a new type called Strings which will contain all our keys:

enum Strings: String {
	case okButtonTitle = "ok_button_title"
	case cancelButtonTitle = "cancel_button_title"
	case mainTitle = "main_title"
	case mainSubtitle = "main_subtitle"
}

We can even define an extension to get the localized string for every key in the Strings enum:

extension Strings {
    var localized: String {
        return NSLocalizedString(rawValue, comment: "")
    }
}

Now all we need to do to use a string in a UILabel, for example, is to call:

label.text = Strings.mainTitle.localized

Bringing it to the next level: extracting the strings

Keeping strings and wordings up to date is usually a burden for developers. Designers would produce a mockup which some wordings than then the product manager will need to check/update. The developer is usually left with the task of making sure that these wordings are in sync. If a project is run in several platforms (iOS, Android, web…), keeping the wordings up to date and synchronized can get out of control easily.

A wiser approach would be to move these strings out of the project and place them in a centralized tool that every platform could access. You could build such a service or you can use a commercial solution. For the sake of this tutorial I’m going to use POEditor: an online tool which allows you define terms and their translations and provides an API to access that information.

Setting up the project

This step is very straight-forward: we create a project, we add new entries (called terms in POEditor) and provide the translations. That’s all it is.

POEditor POEditor

Automatizing the fetching of the strings

The underlying idea is that, on every build, we want to fetch the latest strings, generating our Strings enum automatically (listing all available keys), and download the translations in the different supported languages.

To do so I’m using Python, hacking my way into it (please, don’t judge my Python knowledge: I know nothing about nothing).

400: Invalid request

The script does two main things: first, it fetches all the terms listed in the project. With that we create our Strings class (we are also taking care of the case conversion from snake to camel case).

The second step is to download the files containing the translations.

This script is run during the build phase:

Build Phases Build Phases

Conclusion

This can be easily improved, but with a couple of neat tricks we were able to create a setup where developers don’t really need to worry about strings anymore. A PM or marketing person could go into that tool and update these strings and the app will pick-up on the changes during the next build.

The tool also supports other formats for the export file (JSON, Android XML, etc.), so this script could be easily adapted to your project/platform requirements.

Thanks for reading! ✌️

SwiftUI in the Apple Watch - Baby Colors

My daughter Lola, as any other toddler I suppose, loves screens. She gets hypnotized by any square that throws light. My wife and I have been pretty consequent about her exposure to screens: we don’t watch TV if she is around, we limit our own screen time, etc. Yet there is one last screen my daughter is enjoying: my Apple Watch.

Pagination


© 2021. All rights reserved.

Powered by Hydejack v9.1.6