Lambda Labs Week III

7 minute read

Individual Accomplishments

This week I added the ability to POST daily data up to the back end, and GET the user’s daily data back from the back end. I also added a little more information to the Stats view, and embedded them in a page view controller so that the user can swipe through previous days’ data. I also reconfigured the daily data model a little bit, to make things easier for the web team. Then I added a basic settings view that allows the user to add a reminder for them to go to sleep. Finally, I made some changes to the “sleep tracking view”. I added a “slide to cancel” slider to reduce the risk of the user accidentally cancelling their alarm in their sleep, and only presented the “wake up” and “snooze” buttons once the alarm starts going off. As I was testing these changes and thinking through how someone might use the app, I came to the conclusion that it would probably make more sense to present a different view modally while the alarm is set. That way it can take up the whole screen of the phone and it will make it easier to present surveys before and after sleep.

Settings Screen Updated Stats Screen

Detailed Analysis

The most interesting thing I worked on this week was reworking the flow for setting the alarm and tracking the user’s sleep. As I was going about building out the “Sleep Tracking View” I found that I had a lot of state to manage, and as I added things like snooze buttons or cancel buttons, keeping track of that state got more and more complicated. On top of that, there were various buttons on the screen that the user could accidentally tap in their sleep, depending on where they had their phone. So I decided to split up the functionality into an AlarmSetupViewController , which lets the user set up the alarm for the night, and then presents the SleepTrackingPresentationViewController modally, filling up the whole screen. The presentation view controller is then in charge of presenting (basically as a full screen container view) a PreBedSurveyViewController, a SleepTrackingViewController, and a PostBedSurveyViewController throughout the course of the process. It is also in charge of collecting all the information from those various views into one piece of DailyData.

I am taking a whole different tack for this part of the app (and may go back to refactor everything else) where everything is totally programmatically presented. To make things easier to set up for that, I made a few helper methods in an extension on UIView. I don’t know if it is the best way to organize these things, but it makes the code a lot cleaner in the views that I am writing. I have not spent a lot of time writing views programmatically, but I am enjoying the process and as I do I keep finding ways to abstract and reuse code in various ways. The only thing I don’t like about it is that the results aren’t as immediately visible as they are in interface builder, so I’ve been spending a lot of time building and running on my phone, but I am sure that will become less necessary over time.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// Adds the view it is called on as a subview of the given view and turns translatesAutoresizingMaskIntoConstraints off
private func addAsSubviewWithConstraintsOf(_ view: UIView) {
    self.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(self)
}

/// Adds the view it is called on to the given view and constrains it to fill that view, with options to use the view's safe area and offsets from each side.
func constrainToFill(_ view: UIView, safeArea: Bool = false, top: CGFloat = 0.0, bottom: CGFloat = 0.0, leading: CGFloat = 0.0, trailing: CGFloat = 0.0) {

    addAsSubviewWithConstraintsOf(view)

    let topAnchor = safeArea ? view.safeAreaLayoutGuide.topAnchor : view.topAnchor
    let bottomAnchor = safeArea ? view.safeAreaLayoutGuide.bottomAnchor : view.bottomAnchor
    let leadingAnchor = safeArea ? view.safeAreaLayoutGuide.leadingAnchor : view.leadingAnchor
    let trailingAnchor = safeArea ? view.safeAreaLayoutGuide.trailingAnchor : view.trailingAnchor

    self.topAnchor.constraint(equalTo: topAnchor, constant: top).isActive = true
    bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: bottom).isActive = true
    self.leadingAnchor.constraint(equalTo: leadingAnchor, constant: leading).isActive = true
    trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: trailing).isActive = true
}

I also have methods for constraining a view to its superview, to center it within its superview, and to constrain it to a sibling view. With these extensions, setting up my view is pretty simple, and looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// MARK: - UI Layout
private func setupLabels() {
    if titleLabel == nil {
        titleLabel = UILabel.titleLabel(with: "Sleep Tracking", and: .darkBlue)
        titleLabel!.constrainToSuperView(view, top: 16, leading: 24)
    }

    guard let alarmTime = delegate?.alarmManager.timeString else { return }
    let subtitleString = "Alarm set for: \(alarmTime)"

    if let subtitleLabel = subtitleLabel {
        subtitleLabel.text = subtitleString
    } else {
        subtitleLabel = UILabel.subtitleLabel(with: subtitleString, and: .darkBlue)
        subtitleLabel!.textAlignment = .center
        subtitleLabel!.constrainToCenterIn(view)
    }
}

private func setupCancelSlider() {
    cancelSlider = SLSlideControl()
    cancelSlider!.addTarget(self, action: #selector(cancelAlarm), for: .valueChanged)
    cancelSlider!.constrainToSuperView(view, bottom: 32, centerX: 0)
}

private func setupStackView() {
    stackView = UIStackView()
    stackView!.axis = .vertical
    stackView!.spacing = 48
    stackView!.constrainToCenterIn(view)

    let snoozeButton = UIButton.customButton(with: "Keep Sleeping", with: .negative)
    snoozeButton.addTarget(self, action: #selector(snoozeAlarm), for: .touchUpInside)
    stackView?.addArrangedSubview(snoozeButton)

    let wakeUpButton = UIButton.customButton(with: "Wake Up!")
    wakeUpButton.addTarget(self, action: #selector(turnOffAlarm), for: .touchUpInside)
    stackView?.addArrangedSubview(wakeUpButton)
}
Sleep Tracking Screen I Sleep Tracking Screen II

Team Reflections

My experience working with the team is probably a little bit different than that of the Web devs. Since I am the only iOS dev on the team, I pretty much only have to integrate with my own work. This is a positive and a negative, because it means the set of components that I write are not all that disparate, but at the same time it means that I have to develop a single, cohesive and complete product on my own.

The one area where this is not true is when it comes to the data models/networking. I have to use the same endpoints that the front end uses, I get back data in the same format that they get, and they expect me to send data in the format that they want. This has been the biggest technical sticking point for me, when the data model changes on the backend without warning and I have to make adjustments to my model to keep up. For the most part I have been able to figure it out on my own (a couple of the problems have been on my end anyways). I’ve been using Paw to simulate network requests and that has helped me to quickly diagnose most of the problems I’ve seen. But there was one time where two of my teammates and I just had to get in a Zoom call and talk through why everyone was doing what they were doing, and try to find a solution that worked for everyone. It took about two hours to work through, but we eventually reached an option that would work and in the end all I had to do was write a custom encode(to encoder:) method.