I have a UIView inside a UIScrollView and am using a CAShapeLayer with several sublayers to draw in the view. In some cases the layer is not visible and I'd like to scroll the view so that the layer becomes visible. To scroll I'm using:
[self.scrollView setContentOffset: CGPointMake(0, offset) animated: NO];
I'm having a hard time to figure out what the offset is. I've tried to get the enclosing rect, but that always has the origin at (0,0).
How can I calculate the position of the layer to use in offset?
UPDATE:
This seems to work to get the enclosing rect for all sublayers:
- (CGRect) enclosingLayerRect
{
CGRect rect = CGRectZero;
if (self.sublayers.count)
{
CAShapeLayer *layer = self.sublayers[0]; // need to get the first one, otherwise the origin will be (0,0)
rect = CGPathGetBoundingBox(layer.path);
for (CAShapeLayer *layer in self.sublayers)
{
CGRect layerRect = CGPathGetBoundingBox(layer.path);
rect = CGRectUnion(rect, layerRect);
}
}
return rect;
}
Feel free to comment if there is a better, easier way to do this.
Use the convertRect:toView: method to convert the frame to the scroll view's coordinate system, and the resulting rect should give you the required content offset.
Related
I have following hierarchy: Controller.view [tableView, overlayView]. Table view has top inset of 16 points from parent view via constraint, overlay view - not.
Inside table cell content view I have another view responder and I want to show it's image on view overlayView.
If I use this code (inside overlay view):
CGRect frame;
CGPoint origin = [responder convertPoint:responder.frame.origin toView:self];
// FIXME origin.y -= 16;
frame.origin = origin;
frame.size = responder.frame.size;
_imageView.frame = frame;
UIGraphicsBeginImageContextWithOptions(responder.bounds.size, NO, 0);
[responder drawViewHierarchyInRect:responder.bounds afterScreenUpdates:YES];
_imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I get imageView with 16 points offset from responder origin and image not fit into original view. So I have to use line with FIXME mark to have origin points equal. Why converting don't work?
You should be converting the point from the responder's superview, not the responder itself. Change your second line to this:
CGPoint origin = [responder.superview convertPoint:responder.frame.origin toView:self];
I am doing objective-C app and I want create a UIView with my custom values and there is a problem that i can not fix.
This code works fine:
CGRect rect;
rect.origin.x = 10; rect.origin.y = 20;
rect.size.width = 30; rect.size.height = 40;
NSLog(#" %#",NSStringFromCGRect(rect));
But this one in NSLOG returns origin point to (0,0) and width and height fine:
UIView* aux = [[UIView alloc] initWithFrame:CGRectMake(10,20,30,40)];
NSLog(#" %#",NSStringFromCGRect(aux.bounds));
Is there any problem in initialization of UIView with CGRectMake?
Thanks
You are printing view.bounds, but not view.frame.
Just change it to:
NSLog(#" %#",NSStringFromCGRect(aux.frame));
First a recap on the question: frame, bounds and center and theirs relationships.
Frame A view's frame (CGRect) is the position of its rectangle in the superview's coordinate system. By default it starts at the top left.
Bounds A view's bounds (CGRect) expresses a view rectangle in its own coordinate system.
Center A center is a CGPoint expressed in terms of the superview's coordinate system and it determines the position of the exact center point of the view.
You need to use:
NSLog(#" %#",NSStringFromCGRect(aux.frame));
The origin of the bounds of a view are always (0, 0). You have to change your code to
NSLog(#" %#",NSStringFromCGRect(aux.frame));
See Apple's documentation for an explanation here
The bounds of an UIView is the rectangle, expressed as a location (x,y) and size (width,height) relative to its own coordinate system (0,0).
The frame of an UIView is the rectangle, expressed as a location (x,y) and size (width,height) relative to the superview it is contained within.
So you need to use view.frame in your log method as-
NSLog(#" %#",NSStringFromCGRect(aux.frame));
I'm using OpenTok which is a webRTC framework. What I need to do is take the displayed video view and crop it to a circle. Problem is, since this video avatar view will be placed in a view with a clear background, I can't just use a mask as shown in this S.O. question:
Cut Out Shape with Animation
I've also tried to use layer.radius in a UIView category:
-(void)setRoundedViewToDiameter:(float)newSize;
{
CGPoint saveCenter = self.center;
CGRect newFrame = CGRectMake(self.frame.origin.x, self.frame.origin.y, newSize, newSize);
self.frame = newFrame;
self.layer.cornerRadius = newSize / 2.0;
self.center = saveCenter;
}
And then applied like so:
- (void) setUserVideoView:(UIView *)view {
[view setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
self.userVideo = view;
[self.userVideo setRoundedViewToDiameter:[WSUserView dimForUserAvatar:_sizeIndex]];
[self addSubview:self.userVideo];
[self sendSubviewToBack:self.userVideo];
[self layoutSubviews];
}
But it's still an uncropped rectangle. Here's the portion of the video view. I'm showing user image avatars at first, but then when a video stream connects I want to replace the image with the video view, but as a circle. The left image is the stream view that I need make a circle.
Also, here's the inspector view of the video view I'm trying to crop. As you can see, it's a OTGLKVideoView class.
Migrated from my comment:
You should set self.layer.masksToBounds = YES because this ensures that the layer's sublayers are clipped with the corner radius too. I'm assuming that the problem is arising because the ever-changing sublayer that is updated whenever the video's frame changes is thereby ignoring the corner radius.
More details can be found through this answer which solves a similar problem: https://stackoverflow.com/a/11325605/556479
In my iPad app, Universal Combat Log (new-layout branch), I have a UIView subclass (UCLLineChartView) which contains a UIScrollView and the scrollview in turn contains another UIView subclass (ChartView). ChartView has multiple sub-layers, one for each line of data that has been added to the chart. UCLLineChartView draws the axes and markers. The contents of these views/layers are entirely custom drawn, no stock views are used (e.g. UIImageView).
I'm having a problem with zooming -- it's scaling the ChartView like an image, which makes the drawn line all blurred and stretched. I want the line to stay sharp, preferably even while the user is in the act of zooming, but after 3 days of hacking at this, I cannot get it to work.
If I override setTransform on the ChartView to grab the scale factor from the transform but don't call [super setTransform], then the scrollview's zoomScale stays at 1. I tried keeping the given transform and overriding the transform method to return it. I tried replicating the effects of setTransform by changing the ChartView's center and bounds but I wasn't able to get the behaviour quite right and it still didn't seem to affect the scrollview's zoomScale. It seems that the scrollview's zoomScale depends on the effects of setTransform, but I cannot determine how.
Any assistance would be greatly appreciated.
What you will need to do is update the contentScaleFactor of the chartView. You can do that by adding the following code in either scrollViewDidEndZooming:withView:atScale: or scrollViewDidZoom:.
CGFloat newScale = scrollView.zoomScale * [[UIScreen mainScreen] scale];
[self.chartView setContentScaleFactor:newScale];
I have figured out a solution to my problem that is not too gross a hack. In your UIScrollViewDelegate:
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
[_contentView beginZoom];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
CGSize size = scrollView.bounds.size;
CGPoint contentOffset = _scrollView.contentOffset;
CGFloat newScale = _contentView.scale;
newScale = MAX(newScale, kMinZoomScale);
newScale = MIN(newScale, kMaxZoomScale);
[_scrollView setZoomScale:1.0 animated:NO];
_scrollView.minimumZoomScale = kMinZoomScale / newScale;
_scrollView.maximumZoomScale = kMaxZoomScale / newScale;
_contentView.scale = newScale;
CGSize newContentSize = CGSizeMake(size.width * newScale, size.height);
_contentView.frame = CGRectMake(0, 0, newContentSize.width, newContentSize.height);
_scrollView.contentSize = newContentSize;
[_scrollView setContentOffset:contentOffset animated:NO];
[_contentView updateForNewSize];
[_contentView setNeedsDisplay];
}
In your content view, declare a scale property and the following methods:
- (void)beginZoom
{
_sizeAtZoomStart = CGSizeApplyAffineTransform(self.frame.size, CGAffineTransformMakeScale(1/self.scale, 1));
_scaleAtZoomStart = self.scale;
}
- (void)setTransform:(CGAffineTransform)transform
{
self.scale = _scaleAtZoomStart * transform.a;
self.frame = CGRectMake(0, 0, _sizeAtZoomStart.width * self.scale, _sizeAtZoomStart.height);
[self updateForNewSize];
[self setNeedsDisplay];
}
And if your content view uses sub-layers, you'll need to disable their implicit animations by adding the following to the sub-layers' delegate(s):
- (id<CAAction>)actionForLayer:(CALayer *)layer forKey:(NSString *)event
{
// prevent animation of the individual layers so that zooming doesn't cause weird jitter
return (id<CAAction>)[NSNull null];
}
The basic idea here is that the overridden setTransform uses the scale factor from the tranform matrix to calculate the new scale factor for the content view and then resizes the content view accordingly. The scrollview automatically adjusts the content offset to keep the content view centered.
The scrollViewDidEndZooming code keeps the zooming bounded.
There are further complexities for dealing with resizing the scrollview when rotating device for example.
I always had the impression the origin of a View defines its position inside a parent view. But in my application the Origin is 0,0 no matter where I move my view on the Interface.
This is the code I use:
- (void)drawRect:(CGRect)rect
{
float orginX = rect.origin.x;
float orginY = rect.origin.y;
NSLog(#"X-Origin %f.", orginX);
NSLog(#"Y-Origin %f.", orginY);
Where is my mistake?
The rect passed in to drawRect: is the view's bounds, not it's frame, so its origin will be 0,0.
I believe the drawRect method is passed the frame relative to the view's origin, i.e. the origin will always be 0,0. This is because you should draw your view without knowing or caring where it is in relation to its superview.