UIView's contentScaleFactor depends on implementing drawRect:?

别等时光非礼了梦想. 提交于 2019-12-04 19:22:53

问题


I have stumbled on a weird thing. It looks like UIView's contentScaleFactor is always 1, even on Retina devices, unless you implement drawRect:. Consider this code:

@interface MyView : UIView
@end

@implementation MyView

- (id) initWithFrame: (CGRect) frame
{
    self = [super initWithFrame: frame];
    if (self) {
        NSLog(@"%s %g %g %g", __PRETTY_FUNCTION__, self.contentScaleFactor, self.layer.contentsScale, [UIScreen mainScreen].scale);
    }
    return self;
}

- (void) didMoveToWindow
{
    if (self.window)
        NSLog(@"%s %g %g %g", __PRETTY_FUNCTION__, self.contentScaleFactor, self.layer.contentsScale, [UIScreen mainScreen].scale);
}

@end

On a Retina device it prints the following:

-[MyView initWithFrame:] 1 1 2
-[MyView didMoveToWindow] 1 1 2

If I add an empty implementation of drawRect: like this:

- (void) drawRect: (CGRect) rect
{
}

it works as expected:

-[MyView initWithFrame:] 2 2 2
-[MyView didMoveToWindow] 2 2 2

So it looks like it doesn't really matter if the view is in any view hierarchy and what kind of screen it is displayed on. The only thing that does matter is if the view implements drawRect: or not.

Is that a bug or a feature? I know I can change didMoveToWindow as below to fix it

- (void) didMoveToWindow
{
    if (self.window)
        self.contentScaleFactor = self.window.screen.scale;
}

but the default behavior still bugs me.

You may ask why I need contentScaleFactor at all if I don't draw anything. That's because I just set self.layer.contents to a ready-made image and then stretch the image with contentStretch. However, the image doesn't stretch properly on Retina devices unless contentScaleFactor is set correctly, even though a @2x image is used. To be precise, it works correctly unless a @2x image is used. This is, I guess, a bug.

Can anyone share your insight into why contentScaleFactor behaves this way? Is it specific to iOS 5 only?


回答1:


Presumably, if you don't override drawRect: then UIKit knows that a UIView doesn't draw anything so it takes the (presumably) fast case of having a layer that has a content scale of 1. As soon as you override drawRect: though, it knows it needs to set up a layer that is of the correct content scale that you can draw into if you want to. It doesn't know that you do nothing in drawRect: though so it can't make the same assumption as before.

In fact all that is alluded to in the docs:

For views that implement a custom drawRect: method and are associated with a window, the default value for this property is the scale factor associated with the screen currently displaying the view.

Why don't you just override drawRect: and in that, draw your image? Or you could probably get away with what you're currently doing and have a stub drawRect:. Given what the docs say, I'd say that's perfectly reasonable to assume it's going to continue to work and is correct behaviour.




回答2:


Native drawing technologies, such as Core Graphics, take the current scale factor into account for you. For example, if one of your views implements a drawRect: method, UIKit automatically sets the scale factor for that view to the screen’s scale factor. In addition, UIKit automatically modifies the current transformation matrix of any graphics contexts used during drawing to take into account the view’s scale factor. Thus, any content you draw in your drawRect: method is scaled appropriately for the underlying device’s screen.



来源:https://stackoverflow.com/questions/9479001/uiviews-contentscalefactor-depends-on-implementing-drawrect

工具导航Map