<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>südrocket.de Blog</title>
    <link>https://sudrocket.de/</link>
    <description>Development notes Matthias Maurberger</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>Matthias Maurbeger (matthias@hey.com)</managingEditor>
    <webMaster>Matthias Maurbeger (matthias@hey.com)</webMaster>
    <lastBuildDate>Sat, 08 Feb 2020 19:55:04 +0100</lastBuildDate><atom:link href="https://sudrocket.de/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SwiftUI equal and ideal sizes</title>
      <link>https://sudrocket.de/blog/2022/05/swiftui-equal-and-ideal-sizes/</link>
      <pubDate>Sun, 22 May 2022 20:38:15 +0200</pubDate>
      <author>Matthias Maurbeger (matthias@hey.com)</author>
      <guid>https://sudrocket.de/blog/2022/05/swiftui-equal-and-ideal-sizes/</guid>
      <description>&lt;p&gt;One of the more common sentiments towards SwiftUI you&amp;rsquo;ll come across on the Internet goes something like this: It&amp;rsquo;s magical until it isn&amp;rsquo;t. Meaning: SwiftUI let&amp;rsquo;s you write UI code so quickly that it sometimes feels like a superpower. But suddenly you hit a wall and there&amp;rsquo;s seemingly no way around it. One example that illustrates this quite well is when you want to make all children of a &lt;code&gt;VStack&lt;/code&gt; as wide as the widest child. Sounds like it should be straightforward. But it&amp;rsquo;s not. It&amp;rsquo;s a &lt;em&gt;&lt;a href=&#34;https://swiftbysundell.com/questions/syncing-the-width-or-height-of-two-swiftui-views/&#34; target=&#34;_blank&#34;&gt;surprisingly hard problem.&lt;/a&gt;
&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s stack up all of our favorite movies so we can pick our most favorite among them:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-swfit&#34; data-lang=&#34;swfit&#34;&gt;struct EqualWidthButtonStack: View {
    
    let buttonTitles: [String] = [
        &amp;#34;The Fast and the Furious&amp;#34;,
        &amp;#34;2 Fast 2 Furious&amp;#34;,
        &amp;#34;The Fast and the Furious: Tokyo Drift&amp;#34;,
        &amp;#34;Fast &amp;amp; Furious&amp;#34;,
        &amp;#34;Fast Five&amp;#34;,
        &amp;#34;Fast &amp;amp; Furious 6&amp;#34;,
        &amp;#34;Furious 7&amp;#34;,
        &amp;#34;The Fate of the Furious&amp;#34;,
        &amp;#34;F9&amp;#34;
    ]
    
    var body: some View {
        VStack {
            ForEach(buttonTitles, id: \.self) { t in
                Button(action: {}, label: {
                    Text(t)
                })
                .buttonStyle(.borderedProminent)
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;figure class=&#34;max-w-xs mx-auto&#34;&gt;&lt;img src=&#34;https://sudrocket.de/img/button-width-standard@2x.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;What we see, apart from the fact that it&amp;rsquo;s a very sad top movies list, is that each movie button is as wide as it needs to be to fit the title. Yet we want all of the buttons to have equal widths so our brain isn&amp;rsquo;t tricked to pick the one which stands out the most (&lt;em&gt;Tokyo Drift!!!&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;To achieve this, we&amp;rsquo;ll need to make two changes to our above code. First, we&amp;rsquo;ll wrap each Button&amp;rsquo;s &lt;code&gt;Text&lt;/code&gt; label in a &lt;code&gt;.frame&lt;/code&gt; modifier with &lt;code&gt;maxWidth: .infnity&lt;/code&gt;. That alone only solves our problem if we want our stack of buttons to occupy the full width it was given. If we want the stack of buttons to be as wide as it needs to be, we&amp;rsquo;ll need to make a second adjustment: apply the &lt;code&gt;fixedSize&lt;/code&gt; modifier to the &lt;code&gt;VStack&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-swfit&#34; data-lang=&#34;swfit&#34;&gt;struct EqualWidthButtonStack: View {
    
    let buttonTitles: [String] = [
        &amp;#34;The Fast and the Furious&amp;#34;,
        &amp;#34;2 Fast 2 Furious&amp;#34;,
        &amp;#34;The Fast and the Furious: Tokyo Drift&amp;#34;,
        &amp;#34;Fast &amp;amp; Furious&amp;#34;,
        &amp;#34;Fast Five&amp;#34;,
        &amp;#34;Fast &amp;amp; Furious 6&amp;#34;,
        &amp;#34;Furious 7&amp;#34;,
        &amp;#34;The Fate of the Furious&amp;#34;,
        &amp;#34;F9&amp;#34;
    ]
    
    var body: some View {
        VStack {
            ForEach(buttonTitles, id: \.self) { t in
                Button(action: {}, label: {
                    Text(t).frame(maxWidth: .infinity)
                })
                .buttonStyle(.borderedProminent)
            }
        }.fixedSize()
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This yields the desired result:&lt;/p&gt;
&lt;figure class=&#34;max-w-xs mx-auto&#34;&gt;&lt;img src=&#34;https://sudrocket.de/img/button-width-equal@2x.png&#34;/&gt;
&lt;/figure&gt;

&lt;p&gt;There are, of course, a few other ways to achieve the same result. &lt;a href=&#34;https://swiftbysundell.com/questions/syncing-the-width-or-height-of-two-swiftui-views/&#34; target=&#34;_blank&#34;&gt;John Sundell has a great article with details.&lt;/a&gt;
&lt;/p&gt;
&lt;h3 id=&#34;how-does-it-work&#34;&gt;How does it work&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m probably the wrong person to attempt an explanation how exactly SwfitUI&amp;rsquo;s layout engine works under the hood. I didn&amp;rsquo;t come up with this solution myself. I was taught this technique by &lt;a href=&#34;https://twitter.com/twostraws&#34; target=&#34;_blank&#34;&gt;Paul Hudson&lt;/a&gt;
 in one of his workshops and it kind of blew my mind, because up until then I used the preference system to achieve something like that.&lt;/p&gt;
&lt;p&gt;The key for this technique to work is that even though we wrap all the labels in a &lt;code&gt;frame(maxWidth: .infinity)&lt;/code&gt; modifier the &lt;code&gt;idealWidth&lt;/code&gt; of the &lt;code&gt;Text&lt;/code&gt; is still propagated to the parent &lt;code&gt;VStack&lt;/code&gt;. The &lt;code&gt;VStack&lt;/code&gt; then determines its own &lt;code&gt;idealWidth&lt;/code&gt; by looking at the &lt;code&gt;idealWidth&lt;/code&gt; of all children and picking the widest one. Applying the &lt;a href=&#34;https://developer.apple.com/documentation/swiftui/link/fixedsize%28%29&#34; target=&#34;_blank&#34;&gt;fixedSize&lt;/a&gt;
 modfifier fixes the stack&amp;rsquo;s width at its ideal width (i.e. the width of the widest child).&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://swiftbysundell.com/questions/syncing-the-width-or-height-of-two-swiftui-views/&#34; target=&#34;_blank&#34;&gt;How to sync the width or height of two SwiftUI views?&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.apple.com/documentation/swiftui/link/fixedsize%28%29&#34; target=&#34;_blank&#34;&gt;fixedSize()&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Using awk for project hygiene</title>
      <link>https://sudrocket.de/blog/2022/01/using-awk-for-project-hygiene/</link>
      <pubDate>Mon, 03 Jan 2022 17:29:00 +0100</pubDate>
      <author>Matthias Maurbeger (matthias@hey.com)</author>
      <guid>https://sudrocket.de/blog/2022/01/using-awk-for-project-hygiene/</guid>
      <description>&lt;p&gt;In a somewhat recent &lt;a href=&#34;https://nitter.pussthecat.org/adamgordonbell/status/1476586420781334532&#34; target=&#34;_blank&#34;&gt;tweet&lt;/a&gt;
 Adam Gordon Bell predicted that awk might
still be around in the year 3000 despite being already in use for 44 years:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some software that was written in 2021 will still be in use in the year 3000.&lt;/p&gt;
&lt;p&gt;What will it be?&lt;/p&gt;
&lt;p&gt;Considering that I&amp;rsquo;m using awk today, and it was written 44
years ago, I guessing awk will still be in use then. But what else will make
the cut? Anything made this year?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here&amp;rsquo;s an example where I recently used awk for iOS development: I was working
on a codebase that already went through at least one major change in its
lifetime. Over the years, the codebase accumulated more and more translations.
Some are still in use others are not. The naming convention for the translation
keys also was revised along the way which contributed a lot to translation
corpses being left behind. That&amp;rsquo;s why I wanted to answer the question: &amp;ldquo;Which
keys are still in use now and which keys may be safely deleted from the
project?&amp;rdquo; Let&amp;rsquo;s tackle the question one step at a time:&lt;/p&gt;
&lt;h4 id=&#34;searching-for-a-single-key&#34;&gt;Searching for a single key&lt;/h4&gt;
&lt;p&gt;We can start by checking if one specific key is used in our codebase and then
scale the method we developed for the general case. How do we define &amp;ldquo;is used&amp;rdquo;?
Well, we&amp;rsquo;ll just say that whenever the key comes up in any of the source files
(&lt;code&gt;*.swift&lt;/code&gt;) the key is used. If the project uses Interface Builder we would also
consider storyboard files (and potentially XIB files?). In my case considering
&lt;code&gt;*.swift&lt;/code&gt; files was enough so that&amp;rsquo;s what I&amp;rsquo;m going with here.&lt;/p&gt;
&lt;p&gt;There are several command line tools available for you to find all occurrences
of a given string within the project. My go-to tool in this case is always
&lt;a href=&#34;https://github.com/BurntSushi/ripgrep&#34; target=&#34;_blank&#34;&gt;ripgrep&lt;/a&gt;
 but similar tools like &lt;a href=&#34;https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-grep.html&#34; target=&#34;_blank&#34;&gt;git-grep&lt;/a&gt;
 or &lt;a href=&#34;https://github.com/ggreer/the_silver_searcher&#34; target=&#34;_blank&#34;&gt;the silver
searcher&lt;/a&gt;
 are also a great fit. If we want to know if the key
&lt;code&gt;ERROR_OFFLINE&lt;/code&gt; is used we&amp;rsquo;ll just execute the following command from our
project root:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rg -t swift --case-sensitive -F &lt;span class=&#34;s2&#34;&gt;&amp;#34;ERROR_OFFLINE&amp;#34;&lt;/span&gt; .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s break it down:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-t swift&lt;/code&gt; tells ripgrep that we&amp;rsquo;re only interested in &lt;code&gt;.swift&lt;/code&gt; files&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--case-sensitive&lt;/code&gt; speaks for itself&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-F&lt;/code&gt; tells ripgrep that our search term is a fixed string, meaning that it
should not be treated as a regular expression&lt;/p&gt;
&lt;p&gt;This yields the following output which tells us if and where our key is
currently in use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ rg -t swift --case-sensitive -F &amp;#34;ERROR_OFFLINE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;iOSApplication/Sources/Model/Errors+Localization.swift
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;25:            return LocalizedString(&amp;#34;ERROR_OFFLINE&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;getting-a-list-of-keys&#34;&gt;Getting a list of keys&lt;/h4&gt;
&lt;p&gt;Now that we know how to answer if a specific key is currently in use, we need to
ask this question for all the keys. How do we get this list? This is where awk
comes in. All the keys we might want to check are present in one of our
&lt;code&gt;Localizable.strings&lt;/code&gt; files. Assuming our reference is
&lt;code&gt;en.lproj/Localizable.strings&lt;/code&gt; we need a way to extract just the keys from the
file without the translation. Each &lt;code&gt;Localizable.strings&lt;/code&gt; file contains key-value
pairs which map a key to a corresponding translation:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;#34;ERROR_OFFLINE&amp;#34; = &amp;#34;Why are you offline?!&amp;#34;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Extracting the key in the first column is a prime use-case for awk. I&amp;rsquo;m by no
means an expert in using awk. I&amp;rsquo;ll basically have to look up everything again
when I use it. Here&amp;rsquo;s a great primer that I have bookmarked: &lt;a href=&#34;https://ferd.ca/awk-in-20-minutes.html&#34; target=&#34;_blank&#34;&gt;Awk in 20
Minutes&lt;/a&gt;
. Anyway, let&amp;rsquo;s get the keys out of our file:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ awk &amp;#39;/^\&amp;#34;/ { print $1 }&amp;#39; &amp;lt; Assets/en.lproj/Localizable.strings
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is a bit less readable but it basically just says: Pipe our input file into
awk, only look at lines that begin with a double quote &lt;code&gt;&amp;quot;&lt;/code&gt; and print the first
field. Awk per default separates a line into fields by looking at spaces. &lt;code&gt;print $0&lt;/code&gt; would&amp;rsquo;ve given us the full line, &lt;code&gt;print $2&lt;/code&gt; the equal sign and &lt;code&gt;print $3&lt;/code&gt;
the translation plus the trailing semi-colon. So &lt;code&gt;print $1&lt;/code&gt; it is. And that&amp;rsquo;s
already our list of keys that we need to process further.&lt;/p&gt;
&lt;h4 id=&#34;detect-all-unused-keys&#34;&gt;Detect all unused keys&lt;/h4&gt;
&lt;p&gt;To answer the original question &amp;ldquo;Which keys are safe to delete?&amp;rdquo; we now combine
the two previous mentioned methods. First, we&amp;rsquo;ll use our reference file to get a
list of keys and then we&amp;rsquo;ll search the project using ripgrep for each of those
keys. Combining the two methods could look like the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;!#/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;awk &lt;span class=&#34;s1&#34;&gt;&amp;#39;/^\&amp;#34;/ { print $1 }&amp;#39;&lt;/span&gt; &amp;lt; Assets/en.lproj/Localizable.strings  &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\ &lt;/span&gt;&lt;span class=&#34;c1&#34;&gt;# get a list of keys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; -r KEY&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;                                               &lt;span class=&#34;c1&#34;&gt;# loop over each key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	rg -q -t swift --case-sensitive -F &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$KEY&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; .                     &lt;span class=&#34;c1&#34;&gt;#   browse the project for this key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$?&lt;/span&gt; -eq &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;                                          &lt;span class=&#34;c1&#34;&gt;#   if there&amp;#39;s no match...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;		&lt;span class=&#34;nb&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$KEY&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;                                               &lt;span class=&#34;c1&#34;&gt;#     ... print out the key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;	&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compared to our previous invocation above, we added the &lt;code&gt;-q&lt;/code&gt; (quiet) option to
our ripgrep command to suppress any output from ripgrep. In the following if
statement we inspect the status code of the previous command. &lt;code&gt;rg&lt;/code&gt; will exit with
code &lt;code&gt;1&lt;/code&gt; if it could not find any occurrence and &lt;code&gt;0&lt;/code&gt; otherwise. Every key that
did not come up in our search will be printed out. And that&amp;rsquo;s basically how we
identify unsused keys within our project.&lt;/p&gt;
&lt;h4 id=&#34;caveats--outlook&#34;&gt;Caveats &amp;amp; Outlook&lt;/h4&gt;
&lt;p&gt;Of course the method as written above is not bulletproof for every project. For
example, what if we fetch some keys dynamically from our server? The keys don&amp;rsquo;t
necessarily need to appear in our source files to be important to or project.
However, in my opinion a script like the above does no need to be 100% bullet
proof to be useful. Sure, if you pipe the output of the above script to another
command which just purges all those keys from all &lt;code&gt;Localizable.strings&lt;/code&gt; files it
better be bulletproof. But what we wanted for now is just to get a sense which
of the existing translations are probably obsolete. And it might serve as a
starting point for a more sophisticated script that accomodates the various
intricacies of the project. The main takeaway is this: embrace the shell.
Transforming output with the plethora of commands available to us is such a
powerful tool. And awk is the swiss army knife among those tools.&lt;/p&gt;
&lt;p&gt;P.S.: Drew DeVault has a great blog post about &lt;a href=&#34;https://drewdevault.com/2020/12/12/Shell-literacy.html&#34; target=&#34;_blank&#34;&gt;shell literacy.&lt;/a&gt;
&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
