An alternative to the #warning(…) tag in Xcode

Experienced old Objective-C developers like myself appreciated the #warning(...) tag in source code. It was a great way to remind yourself of things that might otherwise get lost in TODO comments that you forget to search for or your colleague is unaware of. Then it went away in Swift, and people like this guy came up with his own solution, which I liked for a while because it was truly customizable by keyword. Then Apple put #warning(...) back and that became less of an important thing to use.

…. Until I started working on Source Code that is to become a Cocoapod. For if you try to run pod lint spec MySpec.podspec, and there are compiler warnings, the validation process will fail.

So, return of the Run Script that goes through your Swift files and searches for keywords and adds warnings or errors as appropriate.

You can see the original post, or just create a run script (I think before you compile sources but I don’t think it matters), and add this (modify keywords as you like):

TAGS="TODO:|FIXME:"
ERRORTAG="ERROR:"
find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$|($ERRORTAG).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/" | perl -p -e "s/($ERRORTAG)/ error: \$1/"


UIScrollView that adapts to fit content

So I have to say, I have had some difficulty getting UIScrollView to play nicely with Autolayout. There are posts such as here that look to be the solution to many people’s problems. I didn’t really get it working for me.

So I thought I’d write what did. To outline what I’d like to do, I’d like to avoid re-purposing UITableView and cells to display content. It is a lot more boilerplate and it adds complexity. That said, you can have content that is infinitely long and it will be displayed efficiently.

Basically I’d like to create a content view that is potentially larger or shorter than the iPhone screen. Using a UIScrollView is a good candidate for this.

I’ll just get to the summary of the approach. I’ll create a contentView that has its width pinned to the scroll view’s (ultimately the screen’s) width, pinned at the top, but be flexible to allow this contentView to be as large or as small as it needs to be (for example because you have a text view).

I did this by making sure that I pin the scrollView to the superview’s edges, then I add a contentView to the scrollView and pin its top, left, right edges, and pin its width to be the scrollView’s width.

Assuming the component in my contentView is a UITextView, and thus has content that is dynamic, whenever I add this to my storyboard, I make sure that scrolling is disabled on the text view. Then I add an outlet for a NSLayoutConstraint for the text view’s height. I use the delegate callback of UITextView (textViewDidChange) to calculate the ideal height of the text view, then use that height to set the constant of the layout constraint I just mentioned. This resizes the text view to its dynamic content, and thus the size of the contentView.

    func textViewDidChange(_ textView: UITextView) {
        resizeTextViewToFitContent()
    }
    
    func resizeTextViewToFitContent() {
        self.textViewHeightConstraint.constant = self.textView.sizeThatFits(CGSize(width: self.textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude)).height
    }



If I set this up in interface builder and don’t pin the bottom of the contentView, I will get an error about the scrollView having ambiguous content height. This can be suppressed by setting the Ambiguity to “never verify”.

Now all I need is a custom scroll view that will update its content size whenever the contentView changes size:

class ContentScrollView: UIScrollView {
    override func layoutSubviews() {
        super.layoutSubviews()
        
        // alternatively, you can set an IBOutlet weak var contentView: UIView! instead of assuming one subview.
        if let contentView = self.subviews.first {
            self.contentSize = contentView.bounds.size
        }
    }
}

And that’s it! How to make dynamic content be scrollable where required. You’ll notice that if your contentSize is less than the screen height, no scrolling, no scroll indicators; you’ll never even know it’s embedded in a scrollView.

To summarize:

  1. Add UIScrollView inside the main view in Storyboard
  2. Add UIView inside the UIScrollView
  3. Add UITextView inside the UIView (the view added in step 2)
  4. Make sure “Scrolling Enabled” of UITextView is unchecked
  5. Add 4 constraints (leading, trailing, top, bottom) on UIScrollView
  6. Add 3 constraints (leading, trailing, top) on UIView (the view added in step 2)
  7. Add “Width Equally” constraint on UIView (the view added in step 2) and the main view
  8. Add 5 constraints (leading, trailing, top, bottom, height) on UITextView. After this step you shouldn’t get any errors and warnings on constraints.
  9. Add UITextView height constraint IBOutlet on the ViewController. @IBOutlet weak var textViewHeightConstraint: NSLayoutConstraint! and connect it in Storyboard
  10. Make your view controller conform to UITextViewDelegate and implement methods listed above, and make sure the text view’s delegate is this view controller.
  11. If you set the text programmatically, call resizeTextViewToFitContent
  12. In your storyboard, for the scrollView, set Ambiguity to “never verify”
  13. Make sure the UIScrollView is the ContentScrollView given above.

Why is FileManager so unforgiving?

This is more of a reminder for me as to how to do simple things.

Apple keeps modifying the FileManager API, but doesn’t actually make it obvious as to how to do simple things.  I have Data that I want to save somewhere.  You think it would be as easy as getting a path, then writing it.  Nope.

So here’s a recipe to show how to simply write some data somewhere, overwriting as you do it:

func testSerialization() {
        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: self.dates, requiringSecureCoding: false)
            let url = writeLocation()  // currently just the caches directory / SomeFolder / SomeFilename.dta
            let fm = FileManager.default
            if fm.fileExists(atPath: url.path) {
                try fm.removeItem(at: url)
            }
            let folder = url.deletingLastPathComponent()
            try fm.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
            let success = fm.createFile(atPath: url.path, contents: data, attributes: nil)

            XCTAssertTrue(success, "Should have written the file!")
            XCTAssertTrue(fm.fileExists(atPath: url.path), "Should have written something here")

        } catch let error {
            XCTFail("Failed with error: \(error.localizedDescription)")
        }
    }

 

GDPR Compliance and Analytics

This post is more of a conceptual brainstorm about GDPR and still being able to acquire useful analytics data.

If I were to sum up the GDPR, I basically take it to mean that you cannot store/track data about your user without their consent, AND they have the right to be forgotten… i.e. they should be in control of their own data.  This basically means don’t track usage events using a user identifier (on iOS the venerable identifierForVendor property), as this would constitute gathering data about a person that can be inferred via this identifier.

If you can convince your user to opt-in, then great.  Business as usual just with some provisions to be able to delete any data they no longer want you to have/use.  But is there a way to still gather information about App usage without someone’s consent while still obfuscating who actually did the things you’re trying to track?  My understanding is that you can gather all the data you want so long as it’s not possible to trace that back to the user itself.

My thoughts are that if you as an analytics person are able to relax your requirements somewhat, I think it could be possible.  If you as an analytics person don’t need real-time updates on how your users have been using your app, but still want all the same insights, I think it’s still achievable…. if you have patience.

I think the solution is to keep all usage tracking on the device itself, then say once a month you upload it all in one go to some custom API endpoint that would parse all that data into usage stats, all without some user identifier.  The semantics of that are “There is a user – we don’t know who – who used the app in the following ways last month.”  Then you can see funnels.  Then you can see retention.  Then you can see all of those things over a time frame that is useful.

Currently, if we uploaded each and every event as they happened, you’d have no way to connect all those events to a user.  If you upload all of those in one go, with timestamps, you can still process all this data and get a picture of what a user does in a specific amount of time without caring about who it was, specifically.

Whoa. Bush league, Apple, Bush League. (SiriKit woes)

What a highly annoying issue I discovered today!

I was doing some exploratory work with the SiriKit / Intents Frameworks and was very frustrated to find out the following:

If you want to migrate to using a common framework, make sure you have ANY swift type defined in it.  If you only have your .intentdefinition file in that framework, it won’t compile properly, leaving you scratching your head (in my case for HOURS).  Look at target dependencies, looked at linked binaries.  Wanted to look at the generated stuff, but where is it located??

It turns out that as soon as I defined a simple struct that was added to this target, suddenly the generated intents code succeeded.  Until then, you’ll have no idea why.

But wait!  That might have not been it either.  It could also be because of the .intentdefinition file.  I modified a response template and it started working.

So basically under the hood, Xcode is doing weird, buggy stuff.  So it’s maddening when you’re trying to learn something only to find out that you did nothing wrong and the tools are ruining the experience.

Because it’s AUTOMAGIC!

 

Level Up: Test-Driven Development

Until very recently, I’m used to being that hired gun who parachutes in, kills all the work tickets, asks the important questions about the product, makes an app work as desired on a tight deadline to a reasonable level of quality given the constraints, then is out again, all for a reasonably good sum of money. It’s exhausting because these companies often have no established procedures.  So in addition to being Lone Wolf: Kicker of Asses, I’m training junior developers so they can assist me without slowing me down too much, I’m creating an Adhoc QA Department (what is severity, what is reproducibility, how to write bug reports, what is a end-user test suite so you know how to regression test, and why you should use a ticketing system instead of just popping by my desk to effectively pull the plug on whatever 50 things I had in my head), I’m having to interpret incomplete designs, pull the assets out of the Design tools (Zeplin, Sketch, sometimes Photoshop) because many designers don’t know what exactly you need anyway, poke holes and/or fill in the gaps with the UX, and of course manage upwards. Oh yeah, and also do “Agile Waterfall” development, which is borne out of companies who only really do Waterfall but want to be “hip” to the new trends and demand we do Agile (with no scrum master or really anyone who knows how to lead that effectively). So then your time is further taken up with meetings and pushing work tickets around that actually don’t really encapsulate the work you actually need to do, but managers need you to do that so they can generate reports with some data that is meant to impress other people who really have no idea what’s going on anyway, and actually just increased levels of trust in your hires and “we’re good” would be equally effective/ineffective. (Ah, perhaps they don’t know how to make the right hires or can’t get them if they did.) In all of that, I have to “get ‘er done” because the deadline doesn’t change and surprise! All of your dependencies (Design, Backend API) also have the same deadline.

Yikes. A day in the life.  That above would be the worst day in the life.  It’s rarely all of those things.

So I’m grateful for my current freelance contract. It’s the first contract really in years where I felt I’m working in a proper software development company. The management overhead seems low, but the organization is not impacted. They have a process here that works. (I think it’s just because I hit the jackpot and they just placed a priority on the members of the team; personable yet very competent. Ultimately it’s a team who cares about what they do, and making sure there’s a nice team vibe. It also helps that they have corporate backing and therefore have a generous timeframe and budget it would seem.)

“Take the time you need to do a good job.” This is very much the culture here. For one of the first times in my career, I’ve been really exposed  to an office environment where you’re given time to think, and time to write a lot of tests while you develop. You can ask for specifications and those exist and are fixed. There are 2 other iOS devs here to bounce ideas off of, and of course to do code reviews with. It is so extremely satisfying when you get to refactor your original approach into ever more concise code that is more robust and less error prone. Time where you can write the API docs and the Unit Tests to basically freeze the specification. Normally there just isn’t enough time, given all the other tasks that Lone Wolf has, AND the product design always seems to be a moving target.

In short, it feels like I finally have the time and space to level up. Unit Tests are especially awesome when you work in teams and I’m glad for the opportunity to work in this environment for a while so I can establish some good habits and really reach a new plateau in my journey as a software developer.

No, I didn’t die

Looking at this blog, it’s been over a year since I wrote anything. I’ll just list my excuses:

  • I was overworked on some agency projects and there simply wasn’t enough time to write posts
  • It’s generally a grey area to write about problems solved for clients who make me sign an NDA
  • I could have made the solution to the problem look generic, thereby obfuscating my client’s identity and thus my attachment to the project, but didn’t.
  • And now THE main reason….

I spent 8 months of 2018 taking a sabbatical. I like to call it a sabbatical because this is how you don’t lose respect from people when compared to saying “I’m unemployed and am enjoying watching my beard grow”. But really it was more like a “I’m fed up with insane projects that are under-staffed, with poor management and deadlines that don’t change, so ultimately the bottom-feeding developer who is doing the job of a small team on his own is starting to wonder if it’s time to pack it in and open a bakery, or do some other kind of artisanal work that involves repetitive, meditative movements and a certain degree of craftsmanship. <blows sawdust off his handmade hope chest>”

Sabbatical has a nicer ring to it and doesn’t require a backstory.  “Ah, he must be a well-to-do kind of man.”

This sabbatical was not about going to some coding retreat so to level up my skills as a developer. It was more like a sanity check. The reason I like coding and software engineering is that I find it to be a highly creative medium. For me, being creative is what gives life some meaning and makes it fun. The process of having an idea, developing it in your mind, putting it down on paper, refining and iterating on that, solving the unknowns, then finally doing the implementation / building. Then it’s done, you pat yourself on the back, revel in your accomplishments, then say “next!!” I love it. So the sanity check was a) to get a little distance from a profession that was starting to become not fun for me anymore, and b) do something completely different but also creative.

So I did that. In 2018, I decided I wanted to build a camper out of some kind of vehicle, then drive around North America until I experienced some sort of epiphany. I gave myself a year. I used 8 months. I built a camper. I had an adventure. I drove 24,000km in 4.5 months and saw so many beautiful places and met tons of very lovely people. I had some profound realizations about my life that were restorative and empowering for me going forward.

In short it was the most useful year I’ve had in over a decade. If you have the opportunity to take a long period of time off and leave your normal surroundings behind, I highly recommend it.  You’ll return knowing what changes you need to make in life.

True, I have fallen a bit behind with the latest and greatest in iOS / Swift dev, but since I’ve been back working (since November 2018), I’ve been fortunate to work with great developers and I’ve actually leveled up in this short time. I can say I’ve become way more of a test-driven developer, and I’ve also got a few new tricks up my sleeve that you generally get to learn when you work with other developers that work on your platform; not something I’m all that used to as I tend to be either the only iOS dev on a project, or they quickly say “oh but you know the most”.

At any rate, I’m back and I’m still happy to be a developer, and now that my approaches have changed somewhat, I have new areas to grow. So I’m not stagnating like I felt I was, and I’ve been fortunate to get a few freelance contracts that have not turned about to be a complete and total clusterf*ck, which has often been the case in Berlin, Germany. I oftentimes have the feeling that when it comes to Software Development practices, Berlin is the wild west; there are no rules, OR Berlin is teenage love; just fumble around in the dark for a while and hope something good happens.