following WWDC2010 Session 104 I just created a CATiledLayer within a UIScrollView for a 10000 x 8078 px image.
I am troubled with the frame & zoomScale when the view is first shows at its top level
like in session 104 I defined levelsOfDetail as 4
in my drawRect I call CGFloat lScale = CGContextGetCTM(lContext).a;
Strangely at runtime lScale is assigned 0.124907158, not 0.125 as expected.
As well the rect passed to drawRect has floatpoint values, such as
rect.origin.x = 4099.04443
rect.origin.y = 6144
rect.size.width = 2049.52222
rect.size.height = 2048
Most irritating for me is that the frame of the CATiledLayer shows an origin 0 x 26.93 even though I created the tiledImageView using initWithFrame using a 0x0 origin.
Any ideas where I can find the calculation that is responsible for this?
EDIT:
I found the source for the wierd frame position, which is the ScrollView bounds being incorrectly set at 450 px. that should be 420 px. The layoutSubview routine in the UIScrollView has a center view routine that inadvertently adjusted the y coordinate.
I found a cure to this.
The WWDC 2010 session 104 sample has two UIScrollView methods that control the view behaviour.
The first is configureForImageSize that I mentioned in my previously posted answer where the zoomscales are set.
Due to the explained float-point difference the imageView-bounds get generated with the decimals as highlighted before (see wx/hx or wy/hy).
To get clean this up I used the override of layoutSubviews where I calculate the view frame:
- (void) layoutSubviews {
[super layoutSubviews];
CGSize lBoundsSize = self.bounds.size;
CGRect lFrameToCenter = imageView.frame;
// cure to the decimals problem
lFrameToCenter.size.width = roundf(lFrameToCenter.size.width);
lFrameToCenter.size.height = roundf(lFrameToCenter.size.height);
if (lFrameToCenter.size.width < lBoundsSize.width)
lFrameToCenter.origin.x = (lBoundsSize.width - lFrameToCenter.size.width) / 2;
else
lFrameToCenter.origin.x = 0;
if (lFrameToCenter.size.height < lBoundsSize.height)
lFrameToCenter.origin.y = (lBoundsSize.height - lFrameToCenter.size.height) / 2;
else
lFrameToCenter.origin.y = 0;
imageView.frame = lFrameToCenter;
if ([imageView isKindOfClass:[tileView class]]) {
imageView.contentScaleFactor = 1.0;
}
}
Note that I round the frame size before doing anything else with it!
I got a little further in my bug analysis.
Having an mImageSize of 10000x8078 and 320x450 Bounds in the UIScrollView I used the configureForImageSize calculations from the session 104 example as follows:
- (void) configureForImageSize:(CGSize)mImageSize {
CGSize lBoundsSize = [self bounds].size;
// set up our content size and min/max zoomscale
CGFloat lXScale = lBoundsSize.width / mImageSize.width; // the scale needed to perfectly fit the image width-wise
CGFloat lYScale = lBoundsSize.height / mImageSize.height; // the scale needed to perfectly fit the image height-wise
// debug values
float wx = mImageSize.width * lXScale;
float wy = mImageSize.width * lYScale;
float hx = mImageSize.height * lXScale;
float hy = mImageSize.height * lYScale;
...
}
The debugger shows the following values:
* mImageSize = width 10000 height 8078
* bounds size: width 320 height 450
* lXScale = 0.0396137647
* lYScale = 0.0450000018
* wx = 320
* wy = 363.51001
* hx = 396.137634
* hy = 450.000031
so I gather the image size in relation to the bounds leads to a floatpoint calculation difference. When the CATiledLayer is generated the bounds are alway floatpoint values.
If that is the cause, how can I get a mathematical way out of this without having to resize the image (ugly thought)???
Related
For an application I am developing, I let the user specify dimensions of an object they want to capture on camera (for example 30" x 40"). The next thing I want to do, is show the camera feed with a cameraOverlayView on top of it, showing nothing but a stroked transparent square which has the right ratio to capture that object.
So I tried 2 things to get this to work:
Use a UIViewController which uses the AVCaptureVideoPreviewLayer to display a view with the live video feed. On top of that feed I display a transparent view, which draws a square with the right dimensions (using the ratio the user specified).
and
In another attempt I created a UIViewController, containing a button which pops up the UIImagePickerController. Using this controller, I also created a view which I attach to the picker using the cameraOverlayView property.
The main problem I am having with both these methods is that the image that is actually captured is always larger then what I see on screen, but I am not entirely sure how to cut out THAT piece of the image, after the picture has been taken.
So for example:
My UIImagePickerController is shown, I put an overlay over that showing a square that is 300 x 400px large. The user uses this square to take a picture of their object and centers their object inside this square.
The picture is taken, but instead of a picture that is 320x480 (or 640x960) I get a result that is 3500x2400 (or something like that. It's a completely different ratio then the screen ratio (of course).
How do I then make sure I cut out the right part of the image.
The code that actually calculates the size of the square that should be shown (and should be used to determine what piece of the picture should be cut):
+ (CGRect) getFrameRect:(CGRect) rect forSize:(CGSize) frameSize {
if (CGSizeEqualToSize(frameSize, CGSizeZero))
return CGRectZero;
float maxWidth = rect.size.width - 20;
float maxHeight = rect.size.height - 20;
float ratioX = maxWidth / frameSize.width;
float ratioY = maxHeight / frameSize.height;
float ratio = MIN(ratioX, ratioY);
float newWidth = frameSize.width * ratio;
float newHeight = frameSize.height * ratio;
float x = (rect.size.width - newWidth) / 2;
float y = (rect.size.height - newHeight) / 2;
return CGRectMake(x, y, newWidth, newHeight);
}
This determines the largest square that can be created with the ratio specified in the frameSize parameter, with the dimensions where the square should be drawn in supplied in the rect parameter.
Some solutions come to mind, but I am not sure that is doable.
Shrink the photo down to screen width or height (whatever comes first), take the center of the picture and that is the same center that is shown when taking the picture? Not sure this is going to work, I tried this a bit, but that failed.
Ok, I found the solution:
When you are taking a picture with your camera, the preview screen shows only a part of the photo you are taking.
When your iOS device is in portrait mode, the photo height is scaled down to the height of the screen, and only the middle 640px are shown.
The darker red part is what is shown on screen. So when you take a picture, you need to downsize your image to the max height of your screen to get the right width.
After that I cut out the middle 640x960 pixels to actually get the same image as was shown when taking the picture.
After that the coordinates of my rectangular overlay are the same as with my overlay.
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissModalViewControllerAnimated:YES];
UIImage* artImage = [info objectForKey:UIImagePickerControllerOriginalImage];
CGFloat imageHeightRatio = artImage.size.height / 960;
CGFloat imageWidth = artImage.size.width / imageHeightRatio;
CGSize newImageSize = CGSizeMake(imageWidth, 960);
artImage = [artImage imageByScalingProportionallyToSize:newImageSize];
CGRect cutOutRect = CGRectMake((artImage.size.width / 2) - (640 / 2), 0, 640, 960);
artImage = [self imageByCropping:artImage toRect:cutOutRect];
CGRect imageCutRect = [FrameSizeCalculations getFrameRect:CGRectMake(0,0, artImage.size.width, artImage.size.height) forSize:self.frameSize];
artImage = [self imageByCropping:artImage toRect:imageCutRect];
CGRect imageViewRect = CGRectInset(_containmentView.bounds, 10, 10);
NSLog(#"ContainmentView: %f x %f x %f x %f",
_containmentView.frame.origin.x,
_containmentView.frame.origin.y,
_containmentView.frame.size.width,
_containmentView.frame.size.height
);
NSLog(#"imageViewRect: %f x %f x %f x %f",
imageViewRect.origin.x,
imageViewRect.origin.y,
imageViewRect.size.width,
imageViewRect.size.height
);
_imageView.frame = [FrameSizeCalculations getFrameRect:imageViewRect forSize:self.frameSize];
NSLog(#"imageViewRect: %f x %f x %f x %f",
_imageView.frame.origin.x,
_imageView.frame.origin.y,
_imageView.frame.size.width,
_imageView.frame.size.height
);
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.image = artImage;
}
I have a UIScrollView which scrolls horizontally from left to right.
I want to be able to vertically position my UIScrollView on the Y axis according to the screen size of the iPhone. E.g iPhone 4 and iPhone 5.
CGFloat startX = (70.0f * ((float)[_attachments count] - 1.0f) + padding);
CGFloat startY = 295;
CGFloat width = 64;
CGFloat height = 64;
Right now I start my Y position at 295 which works okay with the iPhone 4 but not on iPhone 5.
How would I change the startY to accommodate the same position for different screen size?
CGRect screenBounds = [[UIScreen mainScreen] bounds];
if (screenBounds.size.height == 568)
{
//iphone5
}
else
{
//iphone4
}
You can set your y value accordingly
Do use layoutSubviews in your view ...
- (void)layoutSubviews {
CGRect frame = self.bounds;
frame.origin.y += frame.size.height - _scrollView.frame.size.height;
if ( ! CGRectEqualToRect( _scrollView.frame, frame ) {
_scrollView.frame = frame;
}
}
... I assume that your horizontal scroll view is in _scrollView ivar and also that the _scrollView.frame.size.height contains correct height of your _scrollView. If not, replace _scrollView.frame.size.height with some constant which does contain your UIScrollView height.
Why the condition with CGRectEqualToRect? Because layoutSubviews can be called very often and when you set new frame (even if it equals) to it, UIScrollView can stop scrolling.
you can do it like this-
first get the width and the height of the screen
CGRect screenBounds = [UIScreen mainScreen].bounds ;
CGFloat width = CGRectGetWidth(screenBounds);
CGFloat height = CGRectGetHeight(screenBounds) ;
now create a method which will do some calculations and return an int
-(NSInteger) getwidth:-(NSInteger *) value
{
int temp = (value * 100) / 480;
return int((height * temp) / 100);
}
-(NSInteger) getheight:-(NSInteger *) value
{
var temp = (value * 100) / 320;
return int((width * temp) / 100);
}
now set the bounds of view according to these function
actind.frame=CGRectMake(getWidth(20),getheight(20),16,16);
this will work on all the devices and the views will arrange themselves according to the width and height of the screen.
hope this will help for you. :)
I have a nib with a 2 UIViews and 2 UILabels with IBOutlet's in my controller.
I am increasing and decreasing the height of these UIViews to look like a bar to indicate when something is 100% or less - like a bar chart almost.
I am using the following method for both bars (reuse) however the first UIView (bar) positions correctly but the second is too low down - the Y axis is wrong. However the height of both is correct.
Anyone have any ideas? I have positioned them in interface builder already all I am actually doing in this method is changing the height (basically)
//Position the point, size and label of a rating bar
- (void)configureRatingBar:(UIView *)vRatingBar
andRatingLabel:(UILabel *)lRatingLabel
usingRating:(NSDecimalNumber *)rating
{
//NSDecimalNumber *foodRating = [self calculateRating:self.currentHotel.foodAndBeverageRating];
NSString *formattedRating = [self formatRating:rating];
lRatingLabel.text = formattedRating;
float newHeight = [rating floatValue]; //The height will be based on the rating 100 max
CGRect currentFrame = vRatingBar.frame; //Current frame of the rating view
//CGPoint currentPoint =
// [vRatingBar convertPoint:vRatingBar.frame.origin toView:vReviews];
CGPoint currentPoint = vRatingBar.frame.origin; //Current point of the rating view
float newYaxis = currentPoint.x + (100 - newHeight); //Y axis must move down with the decreasing rating from 0 (100 %) down
[vRatingBar setFrame:CGRectMake(currentPoint.x, newYaxis, currentFrame.size.width, newHeight)];
[lRatingLabel setFrame:CGRectMake(currentFrame.size.width / 34, newHeight - 20, 34, 21)];
}
I see you have this line:
float newYaxis = currentPoint.x + (100 - newHeight); //Y axis must move down with the decreasing rating from 0 (100 %) down
Where you are using currentPoint.x for the calculation. Should this be currentPoint.y?
I have an image that I want to load into an image view and set the minimumZoomScale, as well as the zoomScale to an aspectFill like scale that I calculate as follows:
// configure the map image scroll view
iImageSize = CGSizeMake(iImageView.bounds.size.width, iImageView.bounds.size.height);
iScrollView.minimumZoomScale = iScrollView.bounds.size.height / iImageSize.height;
iScrollView.maximumZoomScale = 2;
iScrollView.zoomScale = iScrollView.minimumZoomScale;
iScrollView.contentOffset = CGPointMake(0, 0);
iScrollView.clipsToBounds = YES;
The iScrollView size is 450 x 320px and the iImageSize is 1600 x 1960px.
Doing the minimumZoomScale math by hand: 450 / 1960 = 0.22959184.
The system determines 0.234693885 instead (???).
But to get the window fit into the 450px space, both figures don't work (!!!).
I manually tried and found 0.207 is the right number (that would translate to a image height of 2174 xp or alternatively a UIScrollView height of 406px).
For information: the UIScrollview screen is 450px, namely 480px minus status bar height (10), minus UITabBar height (20)
Any clues on this misbehaviour?
Not sure if you still have this problem, but for me, the scale is exactly the opposite. minimumZoomScale = 1 for me and I have to calculate the maximumZoomScale.
Here's the code:
[self.imageView setImage:image];
// Makes the content size the same size as the imageView size.
// Since the image size and the scroll view size should be the same, the scroll view shouldn't scroll, only bounce.
self.scrollView.contentSize = self.imageView.frame.size;
// despite what tutorials say, the scale actually goes from one (image sized to fit screen) to max (image at actual resolution)
CGRect scrollViewFrame = self.scrollView.frame;
CGFloat minScale = 1;
// max is calculated by finding the max ratio factor of the image size to the scroll view size (which will change based on the device)
CGFloat scaleWidth = image.size.width / scrollViewFrame.size.width;
CGFloat scaleHeight = image.size.height / scrollViewFrame.size.height;
self.scrollView.maximumZoomScale = MAX(scaleWidth, scaleHeight);
self.scrollView.minimumZoomScale = minScale;
// ensure we are zoomed out fully
self.scrollView.zoomScale = minScale;
For simple zoom in/out i generally use below code. minimumZoomScale= 1 sets initial zoom to current image size. I have given maximumZoomScale = 10 which can be calculated from actual ratio of image size and container frame size.
[scrollView addSubview: iImageView];
scrollView.contentSize = iImageView.frame.size;
scrollView.minimumZoomScale = 1.0;
scrollView.maximumZoomScale = 10;
[iImageView setFrame:CGRectMake(0, 0, 450, 320)];
[scrollView scrollRectToVisible:iImageView.frame animated:YES];
I am loading in images with varying sizes and putting them in UIScrollViews, all the images are larger than the UIScrollView.
The user can scroll and zoom as they please, but initially I would like for the image
to be centered and scaled so the largest side of the image aligns with the edge of the scrollView, i.e. if the picture is in landscape I would like to size and scale it so that the left and right side goes all the way to the edge of the UIScrollVIew and vice versa
I found a formula in a utility function in the Programming guide but it does not quite fit my needs.
My approach is to use:
CGrect initialPos = ?
[self.scrollView zoomToRect:initialPos animated:YES];
I know the size of my scrollView and the size of my image, what I need to figure out is the scale and CGRect to apply to the scrollView to center and size my image.
Hope someone can help out:) Thanks
Edit: previous version was sizing the image to the view rather than the other way around. I think this should correct that:
double imgRatio = imageSize.width / imageSize.height;
double viewRatio = viewSize.width / viewSize.height;
if ( imgRatio >= viewRatio )
{
initialPos.size.width = imageSize.width;
initialPos.size.height = imageSize.width / viewRatio;
initialPos.origin.x = 0;
initialPos.origin.y = (imageSize.height - initialPos.size.height) / 2;
}
else
{
initialPos.size.height = imageSize.height;
initialPos.size.width = imageSize.height * viewRatio;
initialPos.origin.y = 0;
initialPos.origin.x = (imageSize.width - initialPos.size.width) / 2;
}