Issue #124
We all know that there’s a potential crash with UIVisualEffectView
on iOS 11. The fix is to not add sub views directly to UIVisualEffectView
, but to its contentView
. So we should change
effectView.addSubview(button)
to
effectView.contentView.addubView(button)
Here we don’t need to perform iOS version check, because effectView.contentView
works for any iOS versions.
Potential cases for crashes
Here are some cases you can potentially cause the crashes
Strange namings
Normally we name our UIVisualEffectView
as blurView
, effectView
. But there’s times we name it differently like navigationView
, containerView
, boxView
, … This way we may completely forget that it’s a UIVisualEffectView
🙀
containerView.addSubview(button)
boxView.insertSubview(label, at: 0)
Custom loadView
Sometimes it’s convenient to have our UIViewController 's view
as a whole blur view, so that all things inside have a nice blur effect background
class OverlayController: UIViewController {
let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
override func loadView() {
super.loadView()
self.view = blurView
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
}
}
By setting our blurView
as view
in loadView
, we have no idea afterwards that view
is actually a UIVisualEffectView
🙀
Inheritance
What happen if we have another UIViewController
that inherits from our OverlayController
, all it knows about view
is UIView
, it does not know that it is a disguising UIVisualEffectView
🙀
class ClocksController: OverlayController {
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(timeLabel)
}
}
Superclass type
Sometimes declare our things but with protocol or superclass types. Consumers of our API have no clue to know that it is UIVisualEffectView
🙀
let view: UIView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
Here it appears to us that view
is of type UIView
Legacy codebase
Now imagine you ’ve handled a legacy codebase to deal with. Perform finding and replacing all those things related to UIVisualEffectView
is very hard task. Especially since we tend to write less tests for UI
Making it impossible to crash
I like concept like Phantom type to limit interface. Here we’re not using type but a wrapper
final class BlurView: UIView {
private let effectView: UIVisualEffectView
init(style: UIBlurEffectStyle, backgroundColor: UIColor? = nil) {
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: style))
self.effectView.backgroundColor = backgroundColor
super.init(frame: .zero)
insertSubview(effectView, at: 0)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
override func addSubview(_ view: UIView) {
effectView.contentView.addSubview(view)
}
override func layoutSubviews() {
super.layoutSubviews()
effectView.frame = bounds
}
}
Here we override addSubview
to always add views to effectView.contentView
. In the init
method, we need to call insertSubview
instead because of our overriden addSubview
Now BlurView
has a blur effect thanks to is underlying UIVisualEffectView
, but expose only addSubview
because of its UIView
interface. This way it is impossible to cause crashes 😎
let blurView = BlurView(style: .dark)
blurView.addSubview(button(