Swift: Changing UIView frame but display doesn't update

11,652

Solution 1

First, you have a few errors worth noting:

  • Instance variables (like VUBarCover and MainView should always begin with a lowercase letter (like mainView).
  • You should call super.viewDidLayoutSubviews() at the beginning of your override implementation

Second, viewDidLayoutSubviews() can be called any number of times, so your implementation of this method should be idempotent, meaning it has an identical effect no matter how many times it's called. But when you call:

VUBarCover = UIView(frame: frame)
MainView.addSubview(VUBarCover)

You are creating a new UIView and adding it to the view hierarchy, along with the others you may have added in the past. So it looks like the frame isn't updating on the screen. In fact, you just can't tell because the old views are behind the one you're updating and they're not changing.

Instead, you probably want to create this once:

var vuBarCover = UIView()

Add it as a subview in viewDidLoad():

mainView.addSubview(vuBarCover)

And modify its frame in viewDidLayoutSubviews():

vuBarCover.frame = vuBarImageView.frame

You don't need to call setNeedsDisplay().

Solution 2

In my case, there were animations left on the layer, after removing them, the frame changes are updated:

myView.layer.removeAllAnimations()

iOS displays presentationLayer of a UIView on the screen, animations are played on that layer (frame updates goes there during animation).

Solution 3

I am making a custom UIView like you right now, maybe I can help.

Firstly, viewDidLayoutSubviews() like your comment: this gets called after AutoLayout is finished. And after every layout did set, you start to set new frame for your UIView and UIImage, maybe the bug is here. Please try to move your code in viewDidLayoutSubviews() to layoutSubviews(). And don't forget add super.layoutSubviews() in override function, which can help you away from hundreds of bugs :].

Secondly, only if you override the drawRect: method, then you should call setNeedsDisplay(), which is telling system that i need to redraw my view, and system will call drawRect: method appropriately.

Hope you can figure it out.

Share:
11,652
sh37211
Author by

sh37211

They gave me a piece of paper, then a couple postdocs, then they gave me tenure. I'll answer your physics questions if you'll answer my programming and/or signal processing questions. ;-) My fields are relativity, numerical analysis, astrophysics, and acoustics. I program in...whatever's not Ruby, Lisp, or Objective C. I'd like to buy everyone on S.O. a beverage.

Updated on June 04, 2022

Comments

  • sh37211
    sh37211 almost 2 years

    I'm trying to dynamically resize the frame of a UIView. I can update the frame in the code and see that the values have changed, but the actual display never changes, even though I run setNeedsDisplay().

    I'm following this post, where I define a new frame via CGRectMake, and then set the UIView's frame to this new frame. i.e., I'm doing this...

    let newFrame = CGRectMake(minX,minY, width, height)
    VUBarCover.frame = newFrame
    

    If I print out the value of the UIView's frame, I see that it's changing as I like. But the display (in the Simulator) never reflects these changes.

    Any idea how to fix this?

    Update: In greater detail: I'm trying to programmatically cover up one UIView with another. It's a horizontal "VU Meter", consisting of a base UIImageView showing a color gradient, that gets partially dynamically "covered" up by a UIView with a black background. The UIImageView "VUBarImageView" is defined in StoryBoard and is subject to AutoLayout. The black UIView "VUBarCover" is defined purely programmatically, with no AutoLayout constraints.

    Here's the relevant parts of the code for completeness... The trouble I'm having is in updateVUMeter()...

    @IBOutlet weak var VUBarImageView: UIImageView!
    var VUBarCover : UIView!
    
    // this gets called after AutoLayout is finished
        override func viewDidLayoutSubviews() {
            // cover up VU Meter completely
            let center = VUBarImageView.center
            let width = VUBarImageView.frame.width
            let height = VUBarImageView.frame.height
            let frame = VUBarImageView.frame
            VUBarCover = UIView(frame: frame)
            MainView.addSubview(VUBarCover)
            VUBarCover.backgroundColor = UIColor.blueColor() // use black eventually. blue lets us see it for testing
        }
    
     // partially cover up VUBarImage with black VUBarCover coming from the "right"
    func updateVUMeter(inputValue:Float) {   //inputValue is in dB
    
        //let val = pow(10.0,inputValue/10)  // convert from dB
        let val = Float( Double(arc4random())/Double(UInt32.max) ) //for Simulator testing, just use random numbers between 0 and 1
        print("val = ",val)
    
        let fullWidth = VUBarImageView.frame.width
        let maxX = VUBarImageView.frame.maxX
        let width = fullWidth * CGFloat(1.0-val)
        let minX = maxX - width
        let minY = VUBarImageView.frame.minY
        let height = VUBarImageView.frame.height
    
        let newFrame = CGRectMake(minX,minY, width, height)
        VUBarCover.frame = newFrame
        VUBarCover.setNeedsDisplay()
    
        print("fullWidth = ",fullWidth,"width = ",width)
        print(" newFrame = ",newFrame,"VUBarCover.frame = ",VUBarCover.frame)
    }
    

    And sample results are:

    val =  0.9177
    fullWidth =  285.0 width =  23.4556210041046
    newFrame =  (278.544378995895, 93.5, 23.4556210041046, 10.0) VUBarCover.frame =  (278.544378995895, 93.5, 23.4556210041046, 10.0)
    val =  0.878985
    fullWidth =  285.0 width =  34.4891595840454
    newFrame =  (267.510840415955, 93.5, 34.4891595840454, 10.0) VUBarCover.frame =  (267.510840415955, 93.5, 34.4891595840454, 10.0)
    val =  0.955011
    fullWidth =  285.0 width =  12.8218790888786
    newFrame =  (289.178120911121, 93.5, 12.8218790888786, 10.0) VUBarCover.frame =  (289.178120911121, 93.5, 12.8218790888786, 10.0)
    

    ...i.e., we see that VUBarCover.frame is changing 'internally', but on the screen it never changes. What we see instead is the full-size cover, completely covering the image below (ca. 300 pixels wide), even though its width should be only, about 12 pixels).

    Even if I do a setNeedsDisplay() on the superview of VUBarCover, nothing changes.

    Help? Thanks.