Swift and enums #1

: ~1 min read

This will only work for enums that are Ints, start from 0, and increment by 1 - for example tableView / collectionView sections, but I think it's a nice little trick, that I always use:

private enum Section: Int {
  case Products = 0,
  ShippingDetails,
  PaymentDetails,
  Total,
  NumberOfSections
}

Now you can do the following:

func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
  return Section.NumberOfSections.rawValue
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  let cell: UICollectionViewCell

  // Since numberOfSections returns Section.NumberOfSections,
  // it's safe to force unwrap here; it will never crash.
  switch Section(rawValue: indexPath.section)! {
    case .Products: [...]
    case .ShippingDetails: [...]
    case .PaymentDetails: [...]
    case .Total: [...]
    // Now comes the downside: either do a default case,
    // either add the .NumberOfSections case, both of which
    // do nothing, but I think it's worth the minor inconvenience.
    case .NumberOfSections // default:
    assert(false, "Switches must be exhaustive in Swift.")
  }
}

Then, in the future, if the requirements change, and you need the PaymentDetails section to be second, you don't have to remember which index it was and make sure you change it accordingly in didSelectItemAtIndexPath, as well - just move it in front of ShippingDetails inside the enum declaration. Or, if you need to add / remove a section, you don't need to remember to update numberOfSections, it will just work.

SASS' Mixins

: ~40 sec read

I recently saw a nice example of Mixins for SASS. So I went ahead and created a few, since I really dig them:

@mixin responsive_width($width) {
  @media screen and (max-width: $width) { @content; }
}

@include responsive_width(475px) {
  // Custom styles for width <= 475px
}

@mixin light_bottom_border($size) {
  border-bottom: $size solid $light_gray;
}

blockquote {
  @include light_bottom_border(5px);
  @include light_top_border(5px);
}

article {
  @include light_bottom_border(1px);
}

I also unified most of the website's colors with variables like $link_color, $gray_color and $text_color. Maybe one day I will try to unify the paddings and margins as well.

This was fun, and surprisingly satisfying.

Improving the search

: ~40 sec read

I wanted a visual representation and helper for the user when a search is performed on the site. So I thought about autocompleting the searched term in the search field. At first I tried with jQuery:

var query = decodeURIComponent(location.search)
  .split("=")[1]
  .replace(/[\+]/g, " ");
$('input.search').val(query);
$('input.banner-search').val(query);

But this had a small delay and the slight inconvenience of having to replace + with a space, and to decode the URL. So I ended up with some really simple ruby, inside layout.erb:

<% query_params = request.params['query'] %>
<input type="text" [...] value='<%= query_params %>'/>

Fastlane and Alfred

: ~1 min read

One more thing that helps me with the Fastlane flow is an Alfred workflow to run the lanes. It was really easy to create, and here's how: open Alfred, go to Workflows, press +, Templates, Essentials and choose the Keyword to Terminal Command. Give it a name, a description, and a keyword (without a parameter), then paste the following in the script field:

cd ~/path/to/project && fastlane release_minor && exit

This will open the Terminal, cd to that path, run the fastlane command, then close the respective window.

Update, July 16, 2015: Fastlane got a nice update, where you can pass parameteres from the command line, so I improved the Alfred workflow - I set it to require a parameter, with space, then changed the terminal command to:

cd ~/path/to/project && fastlane release scheme:{query} && exit

Now I can write in Alfred release minor, as if I wrote in terminal fastlane release scheme:minor, and Faslane does the same thing as it did before with fastlane release_minor.

Nice.

TextFields with inputView

: ~55 seconds read

Let's say we have a textField with an UIPickerView as its inputView.

The first problem is that we want the textField to not be editable, because we populate it with the value picked from the picker. This is the simple part:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
  return emailTextField == textField
}

The second problem is that the textField is still tappable, it still gets focus and the caret is still visible.

We can't return true on shouldBeginEditing, we can't disable interactions, and resigningFirstResponder doesn't help either, since it closes the picker, and the whole idea is to use the picker natively, without creating a new set of methods that showPicker and hidePicker.

Since a textField's tintColor affects its caret too, the solution for our second problem turned out simple, after all:

emailTextField.tintColor = .clearColor()

Bonus tip:

private var email = "" {
  didSet { emailTextField.text = oldValue }
}

This will automatically update the textField whenever email changes.

Fastlane Deliverfile

: ~56 seconds read

This is the last post about how we use fastlane, and it will present the Deliverfile.

default_language "de-DE"
email itc-username
automatic_release false
skip_pdf true
hide_transporter_output
screenshots_path "../../../../Google Drive/iTunes Assets/images"

if ENV["VERSION_NUMBER"]
  version ENV["VERSION_NUMBER"]
end

changelog(
  "en-US" => File.read("../../../../Google Drive/iTunes Assets/changelog/en.txt"),
  "de-DE" => File.read("../../../../Google Drive/iTunes Assets/changelog/de.txt"),
  "fr-FR" => File.read("../../../../Google Drive/iTunes Assets/changelog/fr.txt"),
  "it-IT" => File.read("../../../../Google Drive/iTunes Assets/changelog/it.txt")
)

success do
  system("say 'Successfully submitted a new version.'")
end

error do |information|
  # custom exception handling here
  raise "Something went wrong: #{information['error']}"
end

Pretty straightforward: set the required variables (this is where ENV["VERSION_NUMBER"] from last time came in handy), while the screenshots and the changelog are in Google Drive, where the guys from the Product and / or Design team can modify if needed. I just need the ok, we can release now after the files have been updated and fastlane lane does the rest.

Awesome.

Fastlane Fastfile #3

: ~2 min read

In the previous post we stopped at incrementing the build number. Now let's talk about the completion. The process is a bit big, but we'll go through it, step by step:

after_all do |lane|
  # Sometimes I just want to test some stuff, so I don't want to post anything on slack
  if lane == :test; clean; next; end

  type = lane == :hockey_debug ? 'Debug' : 'Release'
  # time = Time.now

  slack_params = {
    message: 'iOS App successfully released to Hockey!',
    payload: {
      # 'Date' => "#{t.year}-#{t.month}-#{t.day} #{t.hour}:#{t.min} (#{t.zone})",
      # Because we increase the version after each build,
      # but submit before the increase
      'Build' => "#{build_number.to_i - 1}",
      'Version' => version_number,
      'Type' => type
    },
    default_payloads: [:git_branch, :git_author, :last_git_commit]
  }

  if release_lane lane
    slack_params[:message] = 'iOS App successfully submitted to the App Store!'

    commit_tag_and_update_release_branch
  else
    slack_params[:payload]['Download Link'] = "#{Actions.lane_context[Actions::SharedValues::HOCKEY_DOWNLOAD_LINK]}"
  end

  slack slack_params

  clean
end

error do |lane, exception|
  if release_lane(lane)
    revert_version
  end

  clean
end

def clean
  # Don't clear provisioning profiles, as we store them in git
  clean_build_artifacts exclude_pattern: ".*\.mobileprovision"
end

def release_lane(lane)
  lane.to_s.include? 'release'
end

def revert_version
  Shenzhen::PlistBuddy.set plist, 'CFBundleShortVersionString', @previous_version
end

And the flow would be: after the lane has finished, check if it was a debug lane or not, so we create a proper message and a download link for Debug and Beta builds, create the hash to be sent to Slack, do the required git changhes and clean all artifacts except the mobileprovision files. If the lane failed, revert the version and clean.

The git handling method looks like this:

def commit_tag_and_update_release_branch
  Actions.sh 'git add .'
  Actions.sh "git commit -am \"Version Bump to #{version_number}\""

  Actions.sh "git tag -a #{version_number} -m \"Version #{version_number} submitted to the App Store\""

  # Earlier we already set ensure_git_branch to development, unless we used the :release_quick_fix lane
  # So we know we either are on the development branch, or on a quick-fix one, so it's safe to push and pull-request
  Actions.sh "git push origin #{current_branch}:#{current_branch}"
  Actions.sh "git pull-request -m \"Merging #{version_number}\" -b development -h release"
end

It commits the version bump we did earlier with increase_version, tags the commit with the version_number, pushes the branch and opens a pull request (with the help of hub, a command line wrapper for GitHub).

In the next and final post, Deliverfile.