I am making a small view show up after a long press (iconsContainerView
) and am not understanding why the code in handleLongPress(gesture:)
is executing in the manner that it is. It's my understanding that it should go top to bottom and each line should run immediately. Meaning as soon as view.addSubview(iconsContainerView)
runs, the view should show up in the top left of the screen, as its opacity has not yet been set to 0.
So, the code as written (once the gesture has begun) seems like the view would be shown on the screen in the top left, then move when it's transformed, then disappear (when the opacity is set to 0), then re-appear in the animation when the opacity is set to 1. But what happens is the view doesn't even show up until the code hits the animate block.
So, everything works how I want it to – I do want the subview to fade in after the long press. But I'm just trying to understand what's behind this and why each line of code isn't being immediately executed (or at least showing up on the screen that way). It is running on the main thread, and I've dropped break points in and verified the lines are running in sequence.
class ViewController: UIViewController {
let iconsContainerView: UIView = {
let containerView = UIView()
containerView.backgroundColor = .red
containerView.frame = CGRect(x: 0, y: 0, width: 200, height: 100)
return containerView
}()
override func viewDidLoad() {
super.viewDidLoad()
setUpLongPressGesture()
}
fileprivate func setUpLongPressGesture() {
view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress)))
}
@objc func handleLongPress(gesture: UILongPressGestureRecognizer) {
print("Long gesture", Date())
if gesture.state == .began {
view.addSubview(iconsContainerView)
let pressedLocation = gesture.location(in: view)
let centeredX = (view.frame.width - iconsContainerView.frame.width) / 2
iconsContainerView.transform = CGAffineTransform(translationX: centeredX, y: pressedLocation.y - iconsContainerView.frame.height)
iconsContainerView.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.iconsContainerView.alpha = 1
})
} else if gesture.state == .ended {
iconsContainerView.removeFromSuperview()
}
}
}
I think you're expecting that your code functions like this
you add a subview
system draws the view on the screen
you update the views transform
system redraws the view on the screen
you updates the views alpha
system redraws the view on the screen
Since your code is running on the main thread and the systems drawing code also runs on the main thread, theres no way they both could be running at the same time or flip flopping between the two.
What actually happens is that behind the scenes your app has a loop (a RunLoop) that is always running. The simplest way to think about it is that it
handles input
draws views to the screen
repeat
Your code would fall into the handle input
part. So you whole method has to finish running before the loop can move onto the next step which is drawing the views to the screen. This is also why it is important not to do a lot of work on the main thread, if your method takes a second to run that would mean that the app could not draw to the screen or handle additional input for 1 whole second which would make the app seem frozen.
In reality the main run loop can have a lot more stuff going on in it. It also has a lot of optimizations to make sure its only running when it needs to be to avoid constantly running the cpu or redrawing when nothing has changed which would murder your battery life. This should be enough of an understanding for the majority of iOS development, unless you starting directly interacting with the main run loop or create other run loops but that is rarely needed.