DEV Community

Khoa Pham
Khoa Pham

Posted on

Preventing UIVisualEffectView crash

Original post https://github.com/onmyway133/blog/issues/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)
Enter fullscreen mode Exit fullscreen mode

to

effectView.contentView.addubView(button)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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(
Enter fullscreen mode Exit fullscreen mode

Top comments (0)