UIScrollView Isn't Scrolling - cocoa-touch

Every time this question gets asked on SO (and elsewhere) the answer is "make sure the contentSize is larger than the view size, and set it in viewDidLoad!" Yes, I'm doing that, so what else could be the problem?
In my controller's loadView: I have these lines to set up my view:
descriptionView = [[DetailDescriptionView alloc] initWithFrame:CGRectMake(160, 60, 160, 180)];
descriptionScrollView = [[DetailDescriptionScrollView alloc] initWithFrame:CGRectInset(CGRectMake(0, 0, 160, 180), 5, 5)];
[descriptionView addSubview:descriptionScrollView];
[self.view addSubview:descriptionView];
Then, in viewDidLoad, I have this code:
[descriptionScrollView setDescriptionText:#"<lots of text that will require scrolling>"];
NSUInteger contentHeight = <** calculations on the text to determine height **>
[descriptionScrollView setContentSize:CGSizeMake(descriptionScrollView.frame.size.width, contentHeight)];
NSLog(#"ContentSize: %#", NSStringFromCGSize(descriptionScrollView.contentSize));
NSLog(#"DescriptionScrollView Size: %#", NSStringFromCGRect(descriptionScrollView.frame));
Those NSLogs confirm all should be fine:
ContentSize: {150, 330}
DescriptionScrollView Size: {{5, 5}, {150, 170}}
When it comes to doing the actual drawing of the text, I'm using Core Graphics in the scrollview's drawRect: method. Here's the relevant snippet:
CGRect descriptionTextBox = CGRectMake(self.frame.origin.x, 20, self.frame.size.width - 10, 330);
[self.descriptionText drawInRect:descriptionTextBox withFont:[UIFont fontWithName:kMediumFontName size:kFontSizeMicro]];
I think that covers what I'm trying to do. The scrolling just doesn't occur. Can anyone recommend a tactic to figure out what to try to make this work?

You're drawing in the scrollview's draw rect; it needs to be in a subview of the scrollview.

Related

iOS: Generate PDF from UIView makes text blurry

Generate PDF from UIView by rendering looses quality iOS
I have a custom UIView called TTT_WantsToBeRazorSharpView.
This view does nothing, but draw a text with
NSString*txtPleaseHelp = NSLocalizedString(#"Hello, am I blurry again?",#"");
CGContextShowTextAtPoint(ctx, 10, 50, [txtPleaseHelp cStringUsingEncoding:NSMacOSRomanStringEncoding], [txtPleaseHelp length]);
Now the View is drawn three times to an UIViewController (one time in IB) and two times in code with these lines (compare to image below):
TTT_WantsToBeRazorSharpView *customViewSharp = [[TTT_WantsToBeRazorSharpView alloc] initWithFrame:CGRectMake(10,250, 300, 80)];
[self.view addSubview:customViewSharp];
TTT_WantsToBeRazorSharpView *customViewBlurryButIKnowWhy = [[TTT_WantsToBeRazorSharpView alloc] initWithFrame:CGRectMake(361.5, 251.5, 301.5, 80.5)];
[self.view addSubview:customViewBlurryButIKnowWhy];
the first code drawn view is razorsharp, while the second isn't, but that is ok, because of rect's comma values (361.5, 251.5, 301.5, 80.5).
See this picture:
But my problem is now, if I render the view into an pdf document it is blurry!
And don't know why, see here:
and the PDF file itself Test.pdf
https://raw.github.com/florianbachmann/GeneratePDFfromUIViewButDontWantToLooseQuality/master/Test.pdf
and the lines to render the view into the pdf:
//this is blurry, but why? it can't be the comma coordinates
CGRect customFrame1 = CGRectMake(10,50, 300, 80);
TTT_WantsToBeRazorSharpView *customViewSharp = [[TTT_WantsToBeRazorSharpView alloc] initWithFrame:customFrame1];
CGContextSaveGState(context);
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, (int)customFrame1.origin.x, (int)customFrame1.origin.y);
[customViewSharp.layer renderInContext:context];
CGContextRestoreGState(context);
So why is the renderInContext:context text blurry?
I appreciate all your hints and help, I even made an GitHub project for the brave of you (with source): https://github.com/florianbachmann/GeneratePDFfromUIViewButDontWantToLooseQuality
Try this:
- (void)initValues {
UIColor *gray = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.3f];
CALayer *l = [self layer];
l.masksToBounds = YES;
l.cornerRadius = 10.0f;
l.borderWidth = 3.0f;
self.backgroundColor = gray;
l.backgroundColor = gray.CGColor;
l.borderColor = gray.CGColor;
}
- (void)drawRect:(CGRect)rect {
NSLog(#"TTT_WantsToBeRazorSharpView drawRect[%3.2f,%3.2f][%3.2f,%3.2f]",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
// get the graphic context
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetShouldSmoothFonts(ctx,YES);
CGColorRef colorWhite = CGColorRetain([UIColor redColor].CGColor);
CGContextSetFillColorWithColor(ctx, colorWhite);
CGContextSelectFont(ctx, "Helvetica-Bold", 24, kCGEncodingMacRoman);
CGAffineTransform tranformer = CGAffineTransformMakeScale(1.0, -1.0);
CGContextSetTextMatrix(ctx, tranformer);
//why is this sucker blurry?
NSString*txtPleaseHelp = NSLocalizedString(#"Hello, am I blurry again?",#"");
CGContextShowTextAtPoint(ctx, 10, 50, [txtPleaseHelp cStringUsingEncoding:NSMacOSRomanStringEncoding], [txtPleaseHelp length]);
CGColorRelease(colorWhite);
}
- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
if (!layer.shouldRasterize && !CGRectIsEmpty(UIGraphicsGetPDFContextBounds())) {
[self drawRect:self.bounds];
}
else {
[super drawLayer:layer inContext:ctx];
}
}
I had to change the text color to red to make it visible. Because the text is written first, then the transparent grey button is placed over it, it would make the white text disappear. When adding the drawLayer method, everything that does not need to be rasterized is written as vector to pdf, making the text also selectable.
The text in the view is rendered as bitmap in the PDF and not as vector graphics and this causes the blurry appearance. My guess is iOS rendering engine draws the text on an intermediary bitmap (for color composition purposes I think) and then this bitmap is rendered on the target context.
I have used the following command to fix blurry text:
self.frame = CGRectIntegral(self.frame);
The reason for blurry text was that alignment was not ideal, i.e. fraction values in the point coordinates, so the text appear blurry due to excessive antialiasing.

How to make NSView not clip its bounding area?

I created an empty Cocoa app on Xcode for OS X, and added:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.view = [[NSView alloc] initWithFrame:NSMakeRect(100, 100, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer = [CALayer layer];
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 1, 1, 1);
[self.window.contentView addSubview:self.view];
}
But the rotated layer's background is clipped by the view's bounding area:
I thought since some version of OS X and iOS, the view won't clip the content of its subviews and will show everything inside and outside? On iOS, I do see that behavior, but I wonder why it shows up like that and how to make everything show? (I am already using the most current Xcode 4.4.1 on Mountain Lion).
(note: if you try the code above, you will need to link to Quartz Core, and possibly import the quartz core header, although I wonder why I didn't import the header and it still compiles perfectly)
It turns out that if the line:
((NSView *)self.window.contentView).wantsLayer = YES;
is added to the very beginning, then it works as expected:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
((NSView *)self.window.contentView).wantsLayer = YES;
self.view = [[NSView alloc] initWithFrame:NSMakeRect(200, 200, 200, 200)];
self.view.wantsLayer = YES;
self.view.layer.backgroundColor = [[NSColor yellowColor] CGColor];
[self.window.contentView addSubview:self.view];
self.view.layer.anchorPoint = CGPointMake(0.5, 0.5);
self.view.layer.transform = CATransform3DMakeRotation(30 * M_PI / 180, 0, 0, 1);
}
So it looks like if all the views are made to be layer backed, then it works the same as it does on iOS. (If there is a quick way to make all views layer backed automatically, that'd be good).
the anchorPoint line cannot be moved before addSubview line, or else it is incorrect, although I wonder why that would make any difference.
The line self.view.layer = [CALayer layer]; can be removed if window.contentView is layer backed. Both the contentView and self.view don't need to set the layer, and I wonder why too.
The transform line cannot be before the addSubview line, or else it won't rotate, and I wonder why too.
The third thing is that, I thought if I go to Interface Builder and make the contentView a class of ContentView (subclassing NSView), and in its init method, do a self.wantsLayer = YES;, then it would work too, but it does not.
But anyway, the code above works, and I will update the reasons above why when I find out more.

Why after rotating UIImageView size is getting changed?

I'm new in using transformations. And still confusted how they are working.
What I'm trying to do, is to rotate my UIImageView with given angle. But after rotating, it's changing the size of image, getting smaller. I'm also doing scaling for ImageView so it won't be upside down.How to rotate and keep the size, that was given in CGRectMake, when ImageView was allocated ?
UIImageView *myImageView = [[UIImageView alloc]initWithFrame:CGRectMake(x,y,width,height)];
myImageView.contentMode = UIViewContentModeScaleAspectFit;
[myImageView setImage:[UIImage imageNamed:#"image.png"]];
myImageView.layer.anchorPoint = CGPointMake(0.5,0.5);
CGAffineTransform newTransform;
myImageView.transform = CGAffineTransformMakeScale(1,-1);
newTransform = CGAffineTransformRotate(newTransform, 30*M_PI/180);
[self.window addSubview:myImageView];
Thanks a lot!
Ok I promised I'd look into it, so here's my answer:
I create a scene which should be somewhat equivalent to yours, code as follows:
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.view.bounds.size.width/2-100,
self.view.bounds.size.height/2-125,
200,
250)];
imageView.image = [UIImage imageNamed:#"testimage.jpg"];
imageView.contentMode = UIViewContentModeScaleAspectFill;
/*
* I added clipsToBounds, because my test image didn't have a size of 200x250px
*/
imageView.clipsToBounds = YES;
[self.view addSubview:imageView];
NSLog(#"frame: %#",[NSValue valueWithCGRect:imageView.frame]);
NSLog(#"bounds: %#",[NSValue valueWithCGRect:imageView.bounds]);
imageView.layer.anchorPoint = CGPointMake(0.5, 0.5);
imageView.transform = CGAffineTransformMakeRotation(30*M_PI/180);
NSLog(#"frame after rotation: %#",[NSValue valueWithCGRect:imageView.frame]);
NSLog(#"bounds after rotation: %#",[NSValue valueWithCGRect:imageView.bounds]);
This code assumes that you are using ARC. If not add
[imageView release];
at the end.
Using this code the logs look like this:
[16221:207] frame: NSRect: {{60, 105}, {200, 250}}
[16221:207] bounds: NSRect: {{0, 0}, {200, 250}}
[16221:207] frame after rotation: NSRect: {{10.897461, 71.746826}, {298.20508, 316.50635}}
[16221:207] bounds after rotation: NSRect: {{0, 0}, {200, 250}}
As you can see the bounds always stay the same. What actually changes due to the rotation is the frame, because an image which has been rotated by 30°C is of course wider than if it handn't been rotated. And since the center point has been set to the actual center of the view the origin of the frame also changes (being pushed to the left and the top). Notice that the size of the image itself does not change. I didn't use the scale transformation, since the result can be achieved without scaling.
But to make it clearer here are some pictures for you (0°, 30° 90° rotation):
They already look pretty similar, right? I drew the actual frames to make it clear what's the difference between bounds and frame is. The next one really makes it clear. I overlayed all images, rotating them by the negative degrees with which the UIImageView was rotated, giving the following result:
So you see it's pretty straight forward how to rotate images. Now to your problem that you actually want the frame to stay the same. If you want the final frame to have the size of your original frame (in this example with a width of 200 and a height of 250) then you will have to scale the resulting frame. But this will of course result in scaling of the image, which you do not want. I actually think a larger frame will not be a problem for you - you just need to know that you have to take it into account because of the rotation.
In short: it is not possible to have an UIImageView which will have the same frame after rotation. This isn't possible for any UIView. Just think of a rectangle. If you rotate it, it won't be a rectangle after the rotation, will it?
Of course you could put your UIImageView inside another UIView which will have a non-rotated frame with a width of 200 and a height of 250 but that would just be superficial, since it won't really change the fact that a rotated rectangle has a different width and height than the original.
I hope this helps. :)
Do not set the contentMode which UIImageView inherits from UIView and leads to the changes in frame according to scaling,transformation,device rotation in accordance to the UIViewContentMode you select.
Also if you just want to rotate you can just change the frame using :-
[UIView beginAnimations:#"Rotate" context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationDelegate:self];
CGRect frame=yourView.frame;
frame.origin.y+=distance you want to rotate;
yourview.frame=frame;
[UIView commitAnimations];
}
if you dont want the animation just change the frame
Try Using This :-
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 0.5f;
animation.repeatCount = HUGE_VALF; // HUGE_VALF is defined in math.h so import it
[self.reloadButton.imageView.layer addAnimation:animation forKey:#"rotation"];

Unable to switch between segments for UISegment Control

Below is the code snippet I am using to set up my UISegment Control
//Add UIView below the nav bar
UIView *buttonContainer = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 30)];
[self.view addSubview:buttonContainer];
//Set up segment control
NSString *nicknameLabel = [NSString stringWithFormat:#"%#",self.nickname];
UISegmentedControl *tempSegmentControl = [[UISegmentedControl alloc]initWithItems:[NSArray arrayWithObjects:nicknameLabel,#"Friends", #"Everyone", nil]];
tempSegmentControl.frame = CGRectMake(-8, 44, 336, 30);
self.segmentControl = tempSegmentControl;
[self.segmentControl setWidth:112 forSegmentAtIndex:0];
[self.segmentControl setWidth:112 forSegmentAtIndex:1];
[self.segmentControl setWidth:112 forSegmentAtIndex:2];
self.segmentControl.selectedSegmentIndex = 0;
[self.segmentControl addTarget:self action:#selector(toggleControls:) forControlEvents:UIControlEventValueChanged];
[self.segmentControl setSegmentedControlStyle:UISegmentedControlStylePlain];
[self changeSegmentFontSize];
[tempSegmentControl release];
[buttonContainer addSubview:self.segmentControl];
[self.view bringSubviewToFront:buttonContainer];
[buttonContainer bringSubviewToFront:self.segmentControl];
[buttonContainer release];
However, I in the app, I am unable to switch between the segments for the segmented control. (only show default first segment highlighted)
Any advice on how I can fix this?
It looks like a bounds problem. The containing view has bounds {0, 0, 320, 30} while the segmented control has {-8, 44, 336, 30}. This means the segmented control is technically "outside" its containing view and the iOS hit test will not register a touch.
Try not setting the segmented control's frame (temporarily comment out the tempSegmentControl.frame = CGRectMake(-8, 44, 336, 30); line) and see what happens.
Check that userInteractionEnabled is set to TRUE for your view, buttonContainer, and segment control.
Check that the bounds of the buttonContainer's superview are wide and tall enough to fully contain the buttonContainer. If they're not then hit-testing wont work and the segmented control wont get any touches.

UIScrollView, paging and rotation: Second view is not aligned properly after rotation

I am using a UIScrollView with Paging enabled and the following code to add subviews (core plot charts) to it.
The horizontal scrolling between the views works properly.
However, when showing the second view and then rotating from landscape to portrait mode, the second view is shifted partly to the right and a portion of the first view's right hand side is shown on the left side, hence "destroying" the paging mode.
Could you help me with these issue please? I tried many alternatives, but can't find my bug. Thank you so much!
This is how my iPad screen looks after rotating to portrait mode with the second view:
:
This is my viewDidLoad method:
- (void)viewDidLoad {
self.scrollView.contentSize = CGSizeMake(768 * 2, 400);
chart1 = [[CPTGraphHostingView alloc]initWithFrame:CGRectMake(0, 0, 768, 400)];
chart2 = [[CPTGraphHostingView alloc]initWithFrame:CGRectMake(768, 0, 768, 400)];
self.scrollView.autoresizesSubviews = NO;
chart1.autoresizesSubviews = YES;
chart2.autoresizesSubviews = YES;
self.scrollView.contentOffset = CGPointZero;
self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
chart1.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
chart2.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth ;
[self.scrollView addSubview:chart1];
[self.scrollView addSubview:chart2];
}
This is how I have implemented rotation:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
if (UIDeviceOrientationIsPortrait(fromInterfaceOrientation)) {
self.scrollView.contentSize = CGSizeMake(704 * 2, 400);
chart1.frame = CGRectMake(0, 0, 704, 400);
chart2.frame = CGRectMake(704, 0, 704, 400);
}
else {
self.scrollView.contentSize = CGSizeMake(768 * 2, 400);
chart1.frame = CGRectMake(0, 0, 768, 400);
chart2.frame = CGRectMake(768, 0, 768, 400);
}
}
I think you should change the contentOffset of the scrollview when rotation is taking place.
You should have a way to know which page is currently displayed before rotation (maybe put this information in a variable). Then in your didRotate.. method set the contentOffset of the scrollview after resizing it, like this:
CGFloat offset = self.scrollView.frame.size.width * currentPageIndex;
[self.scrollView setContentOffset: offset];
As an alternative to laying out your subviews in your view controller, have you considered subclassing your UIScrollView and overriding it's layoutSubviews method? You might also consider defining your dimensions as percentages rather than fixed points - because the point values will shift according to rotation and presence of other UI elements such as navigation and toolbars. You may run into trouble as you're manually resizing UI elements in your rotation method, at the same time that the UI is going to be attempting to automatically resize elements according to your resizing masks. Just my thought...