I have set up a simple layout with a single custom GLKViewController. Inside the GLKViewController is a normal GLKView. When the phone orientation changes the view is smoothly autorotated and stretched to match the new size of the view.
Is it possible to acquire the width and the height of the GLKView while the rotation is still in progress, from the glkView:drawInRect: method in my custom GLKViewController?
I'm interested in the width and height marked in blue:
The rotation width and height can be acquired from the presentationLayer of the GLKView:
CALayer *presentationLayer = [self.view.layer presentationLayer];
CGSize layerSize = presentationLayer.bounds.size;
Answered here: https://stackoverflow.com/a/18742030/1109997
Related
I am testing a UIView using a UISlider as in the example images below:
I have a custom UIView with a yellow background that draws the gray square, the drawRect method is like so:
-(void)drawRect:(CGRect)rect{
NSLog(#"Draw rect called");
UIBezierPath* squarePath = [UIBezierPath bezierPathWithRect: CGRectMake(10, 10, 100, 100)];
[UIColor.grayColor setFill];
[squarePath fill];
}
And the method for my slide changing value:
- (IBAction)changeValue:(id)sender {
CGAffineTransform transform = CGAffineTransformScale(CGAffineTransformIdentity, self.slider.value, self.slider.value);
self.tableView.transform = transform;
[self.tableView setNeedsDisplay];
}
I dont understand why the square is getting larger. I've noticed that drawRect is called every time the slider is moved. If this happens then why is the square size changing? Shouldn't it remain the same size and just the frame grow with the square in the top left corner?
My second question is, how would I change the code so just the frame grows and the drawing size stays the same? I ask this because actually I want the drawing size to change dynamically using my own code in drawRect.
Any pointers would be really appreciated! thanks!
The reason why the size of the square changes is because you've transformed it. Transformations don't just affect the frame of a view; they will affect the content. The square is getting drawn into its context at its constant size (100x100) and then the transform is stretching it before it gets rendered.
The reason why it's not expanding to the right and down is because by default the anchor point of a transform is the center of the bounds. Thus it'll scale from the center outwards. From the documentation:
The origin of the transform is the value of the center property ...
Transformations aren't intended to be used to simply scale the width and height of your frame. The frame property is for that. Simply store the view's frame in a variable, change its width and height, then set it back. In your drawRect: code you can check the dimensions of the rectangle that's given to you and make your square's width/height a percentage of that.
Transforming a UIView affects its frame. Transforming a UIView's layer also affects the views frame in the same way. So scaling a view's layer, scales the frame. I'm trying to understand why transforms to the layer affect the views frame (even when view.layer.masksToBounds = NO is set).
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
NSLog(#"Before: %#", NSStringFromCGRect(view.frame));
// Output: {{0, 0}, {50, 50}}
// View transform applied
view.transform = CGAffineTransformMakeScale(2, 2);
NSLog(#"%#", NSStringFromCGRect(view.frame));
// Output: {{-25, -25}, {100, 100}}
// Layer transform applied
view.transform = CGAffineTransformIdentity;
view.layer.transform = CATransform3DMakeScale(2, 2, 1);
NSLog(#"%#", NSStringFromCGRect(view.frame));
// Output: {{-25, -25}, {100, 100}}
You shouldn't look at the frame value once you have a transform, since it's undefined what it contains at that point. This is mentioned in the documentation for the frame property on UIView:
WARNING
If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
If you need that to modify the frame, you have to do so using the center and bounds properties instead.
A frame is a very specific thing.
This rectangle defines the size and position of the view in its superview’s coordinate system. You use this rectangle during layout operations to size and position the view.
Transforms applied to a view effect the origin and size of that view in the superview which is why the view's frame changes.
Transforming subviews will effect the frames of the subviews, but not their superview's frame.
It's worth noting that bounds differs from frame in this respect. The bounds of a view is the origin and size of a view within it's own coordinate system. Transforms should not change a view's bounds, because the transform changes the size and placement of the view for external coordinates, but not the view's internal coordinates.
The frame is a computing property.
Basically, it's synthesized from center and bounds.( To know more, please search for anchorPoint of CALayer).
What's more, when transform is taken into consideration. The frame will be a bounding box that will cover the original box, even rotation or scale is applied.
And the default implementation of hitTest and pointInside will use the final frame, which means you can touch the translated or rotated view normally.
I have UI which hast two states of layouts (besides portrait-landscape). In each state some other part of UI is exposed (is larger).
Problem is that UIScrollView with some UIImageView is resized on those states changes, and in each state different part of image is shown since scale and offset remains unchanged.
Is there some nice way to update this scale and offset values so more or less same part of image is shown for a large and small sized UIScrollView?
How about the UIScrollView method
- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated
Rect is a rectangle in the coordinate space of the view returned by viewForZoomingInScrollView:.
Determine what rectangle of the zoomed view is being shown.
Change your views so that the UIScrollView bounds are changed.
Do the zoomToRect to show the same content, scaled as necessary.
Without having compiled and run this, it should be approximately...
CGSize rectSize;
rectSize.origin = scrollview.contentOffset;
rectSize.width = scrollview.bounds.size.width * scrollview.zoomScale;
rectSize.height = scrollview.bounds.size.height * scrollview.zoomScale;
// Do whatever makes the views change
[scrollView zoomToRect:rectSize animated:whateverYouLike];
I have one component that has an UIView subclass and a custom CAlayer into it.
In the UIView there is a circle that is drawn with CoreGraphics, and this is the code:
CGRect b = self.bounds;
int strokeSize = 2;
CGRect arcBounds = CGRectMake(b.origin.x+1, b.origin.y+1, b.size.width-2, b.size.height-2);
CGContextSaveGState(ctx); {
CGContextSetLineWidth(ctx, strokeSize);
CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
CGContextStrokeEllipseInRect(ctx, arcBounds);
} CGContextRestoreGState(ctx);
when I draw that circle in the drawRect method inside the UIView it works perfect and the circle is drawn smooth and looks great.
The problem appears when I draw another circle just over this one, but the second one is drawn in the CALayer, actually in the drawInContext method of my custom CALayer. Using just the same code the circle doesn't looks good, and have some "pixellation" on the borders.
Any clues on what can be happening? Thanks in advance.
This is due to the contentsScale property. When you have a custom CALayer the default value of this property is 1.0.
The default value of this property is 1.0. For layers attached to a
view, the view changes the scale factor automatically to a value that
is appropriate for the current screen. For layers you create and
manage yourself, you must set the value of this property yourself
based on the resolution of the screen and the content you are
providing. Core Animation uses the value you specify as a cue to
determine how to render your content. Source.
If you have a retina device and you draw with the contentsScale set to 1.0, it will result in that pixelated look you described. In order to fix this you should set the layer's contentsScale to the one of the screen.
[self.layer setContentsScale:[[UIScreen mainScreen] scale]];
This issue does not happen when you draw the circle in the drawRect method of your UIView since there the default contentsScaleFactor is already the one of the screen.
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. Source.
I am putting a UIImageView inside a UIScrollView, and trying to control the image so that it is centred on the scrollview after a zoom. and I am not sure the best way to do this.
The apple docs tell us NOT to use the frame property: "Warning If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored." So I am attempting using the following in a UIViewController subclass whose xib contains a scrollView and contained imageView:
scrollView.bounds =
CGRectMake
(scrollView.contentSize.width/2 - scrollView.center.x,
scrollView.contentSize.height/2 - scrollView.center.y,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
containedView.center =
CGPointMake
(containedView.bounds.size.width*scrollView.zoomScale/2,
containedView.bounds.size.height*scrollView.zoomScale/2);
This works accurately where the width and height of the containedView is larger than that of the scrollView and sets the views so that subsequent scrolling will take you exactly to the edges of the containedView. However when either dimension of the image is smaller than the scrollView width and height the image is magnetically attracted to the top left corner of the screen. In the iPad Simulator (only) when the images is shrunk to the size of minimumZoom it does lock on to the centre of the screen. The magnetic attraction is very smooth as if something in the UI is overriding my code after the image has been centred. It looks a bit like a CALayer contentsGravity ( kCAGravityTopLeft ) thing, maybe?
Apple contradict their own advice in their code sample, photoScroller (in a subclass of UIScrollView):
// center the image as it becomes smaller than the size of the screen
CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = imageView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
frameToCenter.origin.x = 0;
// center vertically
if (frameToCenter.size.height < boundsSize.height)
frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
frameToCenter.origin.y = 0;
imageView.frame = frameToCenter;
This method does a better job of centring when the image is smaller, but when I try this on my project it introduces some kind of inconsistencies. For example, with scrollView.bounces = NO, a horizontal image whose height is smaller than the height of the scrollView but whose width is larger (so it can be scrolled from left to right) will scroll further to the left than it should (when scrolling to the right it stops correctly at the edge of the image, although if scrollView.bounces = YES it then bounces in from the edge so the image is always cropped on the left) When the image is larger in both dimensions than its containing scrollview this issue accentuates and the whole result feels broken, which is unsurprising given Apple's documented advice.
I have scoured the forums and can't find much comment on this. Am I missing something really obvious?
You don't appear to be using the transform property, so you can ignore that warning about not using the frame property when using the transform property. Go ahead and use the frame property, just like Apple (and the rest of us) do.