Huzzah! I just turned in my second major assignment in the Udacity iOS Developer nanodegree program. I had to create a meme-making app called Mememe. I didn't try to live-blog it, but I did take notes, and the app is fun to play with, so here we go... five lessons gleaned and memed!
- Avoid Rabbit Holes
- Sometimes the Obvious Hack is the Right (or Wrong) Answer
- Sometimes an Inelegant Solution Is All You Need
- Be a Detective (Print Statements Are Your Friend)
- When You Hit a Wall, Find a Door
My first task was to set up two text fields with specific formatting for the text. After learning how to set the stroke value to give the text a solid color (It hast to be negative - Who knew?) plus how to specify centered text, all caps, font and point size, here's the question I posed in my notes:
"Why are some text attributes applied using an NSAttributedString array for the defaultTextAttributes property while others, like alignment and capitalization, can only be applied separately using their own properties?"
It's a good question, and I spent too much time researching it before I concluded that the answer is, "Don't worry about it!"
I've gained a little more perspective, looking back. For one thing, this is UIKit stuff, and apparently SwiftUI is the hot new thing, so I'll likely be learning how to do all this again in SwiftUI. Plus unless I end up writing apps where text formatting is a big deal, I don't need to delve into those details.
I tend to want to understand every little thing as I go, but progress is also important. As I gain experience I'll make better judgements about when to spend the time and when to move along. I can always come back and pick up more details if/when I actually need them.
The video instructions suggested I start an app called "Experiment" just to mess around with learning the skills I would need to make the Mememe app. It was never clear to me when to start the actual app, so by the time we got to that, I practically had the whole thing done in my "Experiment" project.
So I did the "obvious hack" and re-named my folder (sorry -> directory), "Mememe 1.0." Well, guess what? Xcode DID NOT TAKE KINDLY TO THAT! Just renaming the app broke it badly; it would no longer compile.
You're probably thinking I captioned my meme wrong by now - it should say "Sometimes the Obvious Hack Is the WRONG Answer."
Bare with me. I spent a huge amount of time trying to fix this mistake by chasing down error messages and changing text in the various files. I kept thinking, one more fix and it'll run. But it never did. Somewhere deep in the bowels of the project, some needle was monkeying up the whole haystack. (Metaphor salad, anyone?)
I started to gird myself for a long and futile online search to figure out how to fix this mess when a simple solution occurred to me - a new hack to fix the old hack. I opened a new project with the name I wanted. Then I copied the text from each of the files in the old project and pasted it into corresponding files in the new project. That got me up and running in moments. See? Sometimes the obvious hack is the right answer.
One of the requirements of this project was that we move the lower text field up out of the way when the keyboard shows on screen. The solution they gave was to retrieve the height of the keyboard and move the entire view up by that amount.
I thought applying this to one specific text field and moving the screen up based on just the keyboard height while ignoring the text field placement seemed clunky. It's a solution that only works based on the particulars of this layout.
So I got fancy and wrote a function to calculate what size nudge it would take to park any given text field exactly on top of the keyboard. I could not get my numbers to work out correctly.
I dove right down that rabbit hole. I learned a lot about relative coordinate systems within different views, but I never could get my calculations to work. I suspect it had to do with the view's auto-formatting moving things around.
I was tempted to attempt an auto-constraint solution, using the keyboard template, which seems like a much more elegant idea, if it would work. But... constraints are my nemesis!
In the end I went back to what the lesson told me to do in the first place. (I also added in a boolean variable to track whether the view was up or down so I wouldn't inadvertently move it up twice.)
It seems like a cobbled together solution to me, but it works. I'll think twice next time I'm tempted to make the code pretty, especially if it doesn't really affect the user's experience or the app's functionality or readability. If it works, it works!
Yeah, that's me during COVID.
This project taught me out of necessity how to hunt down villains using strategically placed
Print statements can tell you when a particular piece of code runs as well as what values the variables hold at a given point. They can tell you whether an if statement executes or a function is called.
I used this method to solve several mysteries, including The Case of the Double Jumper (alluded to parenthetically above) and The Case of the Mutating Text Field:
I had placed code in my "ViewDidLoad" function to set placeholder values for my text fields. Those shouldn't have changed, but now and then, under mysterious circumstances, the placeholder text would suddenly match the current text in the fields.
The only place in my code where those values were ever set was in "ViewDidLoad," which I knew in my heart was only called once at startup seeing as how this was a single-view app. So what was happening - impossible!
Imagine my surprise when a print statement revealed that the ViewDidLoad function was in fact called multiple times! A quick check with ChatGPT provided the explanation: the view had to be re-drawn whenever the device switched between portrait and landscape modes. Seems obvious to me now! But it weren't for that well-placed print statement I still wouldn't have a clue.
This lesson applies to all the problems I ran into. But my biggest frustration came at the end. I had everything working great, except when I turned the phone to landscape mode, my toolbar disappeared.
The app is required to work as expected in landscape and portrait mode. So this needed to get fixed!
I spent hours. There seemed to be no information online about this problem. Chat GPT was giving me nonsense suggestions that led nowhere. I suspected my nemesis, the dastardly constraints! So I poured over them to no avail. Last night I went to bed thinking I'd never ever ever figure it out.
This morning I read through the project's rubric out of desperation. I needed to be doing something - it's all about showing up, right? So I figured I could at least make sure my app was meeting all the other requirements (yes). And there it was! Right in the friggin project requirements!! The image view content mode needed to be set to "aspect fit." I had it on "aspect fill." Duh, right?
However, making that fix left empty space around the image in landscape view. I quickly devised a creative solution and spent the morning enabling the user to apply two different crop frames - one for portrait mode and one for landscape.
Then I finally turned that sucker in!
There's always a solution. Keys to finding it include: be persistent, get rest, approach with fresh eyes, ask questions, and do what you CAN do. The solution will eventually present itself!
Well, not really. I learned a lot more making this app. But these five things are plenty enough to write about. To recap:
- Don't go down un-necessary rabbit holes; come back for the details when you actually need them.
- Sometimes the obvious hack is what you need; sometimes it's very much not. That sounds unhelpful. How about: Got a simple solution that seems too good to be true? Don't discount it, but do think twice. Or maybe just: NEVER EVER EVER rename an Xcode project!
- Sometimes an inelegant solution is all you need. Before you put in the effort to beautify it, ask yourself, will this make the app work better? Will it be easier to follow? Will it make the user experience better? If there's no reason to fix it, let it be.
- Become a detective! Use print statements to figure out what's going on inside your code.
- When you hit a wall, find a door. Take a break, do what you can do, put in the time, and you'll get there.
I hope these serve you well. I know they will me, if I manage to keep them in mind. The memes should help. If you're interested in checking out the app for yourself, it's here on GitHub: Monty's Mememe 1.1