How to prevent UIVisualEffectView crash

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

1
effectView.addSubview(button)

to

1
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 🙀

1
2
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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 🙀

1
2
3
4
5
6
7
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 🙀

1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 😎

1
2
let blurView = BlurView(style: .dark)
blurView.addSubview(button(

Comments