The background: I answered a user question about how to create a blurred image of a text view's contents using simple demo code.
I then decided to generalize my solution and refactored my demo app to create a BlurringView custom subclass of UIView that has blur bool and a child view. It manages a UIImageView that will display a blurred version of the child view's contents if the blur bool is true.
It works fairly well, but I ran into a problem that I can't solve cleanly when the child view is a UITextView:
If you rotate the device, the text view's bounds change and the text view re-flows it's text in the new bounds, but only after the device rotation animation completes.
There are various ways to get notified when the BlurringView is resized: You can add a didSet to the view's bounds; For device rotations you can implement a traitCollectionDidChange() method, you can listen for device rotation methods, and so on.
HOWEVER, all of those methods get triggered before the text view has re-flowed its contents into the new bounds, so if I update my blur view immediately when I get called by any of those methods, I fail to render a blurred version of the updated text layout.
The only way I've been able to get the blurred contents to update correctly is to delay updating the blur view for 0.33 seconds (1/3rd of a second) after being told that the text view has changed (via traitCollectionDidChange, a change to the view's bounds, a rotation notification, or any of those other ways of detecting the change.)
Is there a clean way to know when a text view has finished re-flowing its text contents into a new bounding box? (Granted, it might require that I add special case handling when my child view is a UITextView.)
My sample project is here. It currently uses the traitCollectionDidChange() method to detect that the text view has updated, and then waits 0.33 seconds before updating the blur view:
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
print("In \(#function))")
//The below works to update the blur image after the TextView's frame changes, but only if we add a "magic number" delay of .33 seconds
DispatchQueue.main.asyncAfter(deadline: .now() + 0.33) {
print("Updating blur image. Bounds = \(self.bounds)")
self.updateBlurImage()
}
}
The same 0.33 second delay approach also works in a didSet on the view's bounds, or if I listen for device rotation notifications.
Looking for changes in the view's bounds is probably the best place for the current "hacky" approach, since that will get triggered even if the BlurringView is used in a context where it gets resized for some reason other than a device rotation.
Possibly worth a try...
Move the "blur" code from the switch action to a func:
extend the view controller to conform to
NSLayoutManagerDelegate:In
viewDidLoad():Result looks much better with
blurView.contentMode = .scaleToFillEdit -- changed the "update" call in
didCompleteLayoutForto async.Edit - weird animation layout...
Source:
and debug console output:
Note that when growing the width:
didCompleteLayoutForis calledboundsis setlayoutSubviewsis calledwhen shrinking the width:
boundsis setdidCompleteLayoutForis calledlayoutSubviewsis NOT called