Thinking outside of the box

:  ~ 1 min read

Recently, I stumbled upon a small problem: we have a product details controller which calls a factory method that creates a view with a few UITextViews that detect a website, a phone, and an address, respectively, but the latter was not working reliably. Said method only needs the product object to be passed in to properly create the view.

One simple solution would have been to also pass the controller as the target, and a local method as the selector, replace the UITextView with a button, set its title to the address and its target & action to the passed in params. Then, inside the product details controller we'd have access to product.mapUrl, which points to Google Maps.

But I didn't want to pass those two extra parameters just for this edge case, so after a bit of tinkering around, the solution was to let the UITextView display the address, remove its detectors and create a button with the same frame, set its title to product.mapUrl.absoluteString and its color to .clear, set its target to the factory class, and its action to a new static method.

Now, button's action method has a sender parameter, which can be used to fetch that mapUrl:

// Has to be class because of the button's action, but we could also create an umbrella class, just for that.
class Factory {

	static func contactInformationView(for product: Product) -> UIView {
		let container = UIView()
		[...]
		addressTextView.dataDetectorTypes = []
		addressTextView.text = product.address
		addressTextView.textColor = .actionColor // Same as other textViews' linkTextAttributes.
		[...]
		let addressButton = UIButton(type: .custom)
		container.addSubview(addressButton)

		addressButton.frame = addressTextView.frame // Or align them with Auto Layout.
		addressButton.addTarget(self, action: #selector(addressTapped(_:)), for: .touchUpInside)
		addressButton.setTitle(product.mapUrl.absoluteString, for: .normal)
		addressButton.setTitleColor(.clear, for: .normal)
		[...]
		return container
	}

	@objc private static func addressTapped(_ sender: UIButton) {
		guard
			let title = sender.title(for: .normal),
			let url = URL(string: title)
			else { return }

		// Do something with url.
	}

}

This might look like sacrilege to some, but "If it's stupid but it works, it's not stupid" ¯\_(ツ)_/¯. Besides, the point of the post was that sometimes you might find solutions in the most obscure places. Let me know what you think @rolandleth.