UIPageViewController changes size? - view

I'm using the Page-Based Application template in Xcode 4 to load pages of a PDF and create a UITextView over hidden text boxes so the user can write notes.
So far I have it all working, but when I add the UITextView, it's in the wrong place in landscape mode (showing 2 pages).
// ModelController.m
- (id)init
{
self = [super init];
if (self) {
NSString *pathToPdfDoc = [[NSBundle mainBundle] pathForResource:#"My PDF File" ofType:#"pdf"];
NSURL *pdfUrl = [NSURL fileURLWithPath:pathToPdfDoc];
self.pageData = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfUrl); // pageData holds the PDF file
}
return self;
}
- (DataViewController *)viewControllerAtIndex:(NSUInteger)index storyboard:(UIStoryboard *)storyboard
{
// Return the data view controller for the given index.
if( CGPDFDocumentGetNumberOfPages( self.pageData ) == 0 || (index >= CGPDFDocumentGetNumberOfPages( self.pageData )))
return nil;
// Create a new view controller and pass suitable data.
DataViewController *dataViewController = [storyboard instantiateViewControllerWithIdentifier:#"DataViewController"];
dataViewController.dataObject = CGPDFDocumentGetPage( self.pageData, index + 1 ); // dataObject holds the page of the PDF file
[dataViewController view]; // make sure the view is loaded so that all subviews can be accessed
UITextView *textView = [[UITextView alloc] initWithFrame:CGRectMake( 10, 20, 30, 40 )];
textView.layer.borderWidth = 1.0f;
textView.layer.borderColor = [[UIColor grayColor] CGColor];
[dataViewController.dataView addSubview:textView]; // dataView is a subview of dataViewController.view in the storyboard/xib
CGRect viewFrame = dataViewController.dataView.frame; // <- *** THIS IS THE WRONG SIZE IN LANDSCAPE ***
}
This behavior really surprised me, because viewControllerAtIndex isn't called when I rotate the iPad, so I have no way of knowing what the real size of the view frame is. I get the same view frame in both portrait and landscape:
# in Xcode console:
po [dataViewController view]
# result in either orientation:
(id) $4 = 0x0015d160 <UIView: 0x15d160; frame = (0 20; 768 1004); autoresize = RM+BM; layer = <CALayer: 0x15d190>>
#
Does anyone know if there is a transform I'm supposed to use to position the UITextView correctly? I'm concerned that I may have to store the locations of the elements independently and reposition them upon receiving shouldAutorotateToInterfaceOrientation messages.
It seems that Apple may have implemented UIPageViewController improperly, but all I could find was this partial workaround that I'm still trying to figure out:
UIPageViewController and off screen orientation changes
Thanks!

I think the trick here is to override viewDidLayoutSubviews in your DataViewController and manage the size of all your programmatically-inserted non-autosizing views, since you don't really know what the parent is going to do to its subviews until that time.
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.textView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
}

Related

Label on UIButton not laid out correctly with autolayout

I have an app which I'm updating to auto layout and size classes and I'm getting some weird behaviour with the label on a button.
The button should be a circle and have a label in the centre. I'm implementing my own subclass so I can reuse it.
Here's the storyboard:
and the code for the class which extends UIButton:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setBackgroundImage:[UIImage imageNamed:#"selected-green"] forState:UIControlStateHighlighted ];
self.layer.borderColor = [UIColor tlbWhiteColor].CGColor;
self.layer.borderWidth = 10;
}
return self;
}
-(void) layoutSubviews {
self.layer.cornerRadius = self.frame.size.width / 2;
}
With this the appears as expected but there is no label. On debugging I see that the frame of the label has 0 height and width. So I extended layoutSubviews:
-(void) layoutSubviews {
self.layer.cornerRadius = self.frame.size.width / 2;
if (self.titleLabel.frame.size.width == 0) {
[self.titleLabel sizeToFit];
[self setNeedsLayout];
[self layoutIfNeeded];
}
}
The label then appears, but it's in the wrong place:
The only extra info I can offer is that in Reveal the button has weird height and width constraints added:
The titleInsets are all at 0.
Help hugely appreciated.
I don't think you need the layoutSubviews override. I think it's a hack and it's hiding your real issue.
What are the constraints on the 'Go' label? How are you centering it in the UIButton? Also what is the height constraint on the label?
My suggestion would be to put both the UIButton and the Go label inside a container view and centre the label inside the container view. The container view should have the same height and width as the UIButton inside it.
Also are you changing the frames of the views programmatically somewhere in the app? Those weird constraints you talk about in reveal point to that.
As per your requirements:
- (void)viewDidLoad {
[super viewDidLoad];
view_1.layer.cornerRadius = 10; //view_1 is white color
view_1.layer.masksToBounds = YES;
view_2.layer.cornerRadius = _view_2.frame.size.width/2;//view_2 is green color
view_2.layer.masksToBounds = YES;
}
output
Ok, this was a real school boy error.
When the docs said 'The implementation of this method is empty before iOS 6' I for some reason took this to mean there's no need to call super on layoutSubviews.
So the fix was:
-(void) layoutSubviews {
[super layoutSubviews]; // <<<<< THIS WAS MISSING
self.layer.cornerRadius = self.frame.size.width / 2;
self.layer.masksToBounds = YES;
}

How do I add a NSButton to a CALayer?

I am trying to add a NSButton on a layer inside a IKImageBrowserCell object. I found this post helpful but it doesn't get into the crux.
I've already tried this:
- (CALayer *) layerForType:(NSString*) type
{
CGColorRef color;
//retrieve some usefull rects
NSRect frame = [self frame];
NSRect imageFrame = [self imageFrame];
NSRect relativeImageFrame = NSMakeRect(imageFrame.origin.x - frame.origin.x, imageFrame.origin.y - frame.origin.y, imageFrame.size.width, imageFrame.size.height);
/* foreground layer */
if(type == IKImageBrowserCellForegroundLayer){
//no foreground layer on place holders
if([self cellState] != IKImageStateReady)
return nil;
//create a foreground layer that will contain several childs layer
CALayer *layer = [CALayer layer];
layer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
//add a checkbox to tell whether to upload this one or not
NSRect checkFrame = NSMakeRect( ( frame.size.width/2)-5 , frame.size.height - 19, 18,18);
NSButton *uploadCheckBox = [[NSButton alloc] initWithFrame:checkFrame];
[uploadCheckBox setButtonType:NSSwitchButton];
[layer addSublayer :[uploadCheckBox layer]];
return layer;
}
//(...)
return nil;
}
But unfortunately the button doesn't appear on the layer. I think the placement of the button is fine, since it's based on an example code from Apple's app. I've got a feeling that this line is wrong:
layer addSublayer :[uploadCheckBox layer]];, since I should be adding entire NSButton, not just it's bitmap representation (a layer). Any help greatly appreciated!
You cannot add a NSView inside a CALayer. You should create a new layer for your button purpose and add to your holding layer.

Zoom & Pan in UIImageView inside UIScrollView

I have a UIScrollView filled with UIImageView. The UIScrollView are paging enabled to let the users to "flip" those images. Each UIImageView has UIPinchGestureRecognizer for pinch zoom, and UIPanGestureRecognizer for to pan that image when zoomed in.
As probably you have noticed, what I'd like to achieve is just like what iBooks does in its application.
However, I have difficult times to get this work.
In my "BookPageViewController", I have set up UIScrollView, then fill them with images from the folder based on data (page numbers, file names, etc) from sqlite.
_pageScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
NSUInteger pageCount = [pages count];
for (int i = 0; i < pageCount; i++) {
CGFloat x = i * self.view.frame.size.width;
// Page Settings
UIImageView *pageView = [[UIImageView alloc] initWithFrame:CGRectMake(x, 0, self.view.frame.size.width, self.view.frame.size.height)];
pageView.backgroundColor = [UIColor greenColor];
pageView.image = [pages objectAtIndex:i];
pageView.userInteractionEnabled = YES;
pageView.tag = i;
// Gesture Reocgnisers
UIPinchGestureRecognizer *pinchGr = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(pinchImage:)];
pinchGr.delegate = self;
[pageView addGestureRecognizer:pinchGr];
UIPanGestureRecognizer *panGr = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panImage:)];
[pageView addGestureRecognizer:panGr];
// Finally add this page to the ScrollView
[_pageScrollView addSubview:pageView];
}
_pageScrollView.contentSize = CGSizeMake(self.view.frame.size.width * pageCount, self.view.frame.size.height);
_pageScrollView.pagingEnabled = YES;
_pageScrollView.delegate = self;
[self.view addSubview:_pageScrollView];
And with the help of another good questions here in Stackoverflow, I have put those:
- (void) pinchImage:(UIPinchGestureRecognizer*)pgr {
[self adjustAnchorPointForGestureRecognizer:pgr];
if ([pgr state] == UIGestureRecognizerStateBegan || [pgr state] == UIGestureRecognizerStateChanged) {
if ([pgr scale]) {
NSLog(#"SCALE: %f", [pgr scale]);
[pgr view].transform = CGAffineTransformScale([[pgr view] transform], [pgr scale], [pgr scale]);
[pgr setScale:1];
} else {
NSLog(#"[PAMPHLET PAGE]: The image cannot be scaled.");
}
}
}
My problem is, when I zoom in the one of the UIImageView with Pinch Gesture, the image exceeds (go over) to the next image and hides it. I believe that there should be the way to "limit" the zoom in/out and the size of UIImageView (not UIImage itself), but I don't know where to go from here. I have also put a code to limit the scale something like:
([pgr scale] > 1.0f && [pgr scale] < 1.014719) || ([pgr scale] < 1.0f && [pgr scale] > 0.98f)
but it didn't work...
I know it's not hard for ios professionals, but I'm quite new to objective-c, and this is my first time to develop real application. If this is not a good practice, I also would like to know any ideas to achieve just how to do this like ibooks do (e.g. put UIImageView in UIScrollView, then put the UIScrollView to another UIScrollView)...
Sorry for another beginner question, but I really need help.
Thanks in advance.
Without trying it out myself, my first guess would be that the transform on the UIImageView also transforms its frame. One way to solve that, would be to put the UIImageView in another UIView, and put that UIView in the UIScrollView. Your gesture recognizers and the transform would still be on the UIImageView. Make sure the UIView's clipsToBounds property is set to YES.

CGGradient isn't visible (not using interface builder) and UIButtons can't be triggered

I have created a view that contains a CGGradient:
// Bar ContextRef
CGRect bar = CGRectMake(0, screenHeight-staffAlignment, screenWidth, barWidth);
CGContextRef barContext = UIGraphicsGetCurrentContext();
CGContextSaveGState(barContext);
CGContextClipToRect(barContext,bar);
// Bar GradientRef
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat components[16] = { 1.0,1.0,1.0,0.0, 0.0,0.0,0.0,1.0, 0.0,0.0,0.0,1.0, 1.0,1.0,1.0,0.0};
CGFloat locations[4] = {0.95,0.85,0.15,0.05};
size_t count = 4;
CGGradientRef gradientRef = CGGradientCreateWithColorComponents(colorSpace, components, locations, count);
// Draw Bar
CGPoint startPoint = {0.0,0.0};
CGPoint endPoint = {screenWidth,0.0};
CGContextDrawLinearGradient(barContext, gradientRef, startPoint, endPoint, 0);
CGContextRestoreGState(barContext);
This code is called in the drawRect method of the UIView. I then use a UIViewController to access the created view.
- (void)loadView {
MainPageView *mpView = [[MainPageView alloc] initWithFrame:[window bounds]];
[self setView:mpView];
[mpView release];
}
and displayed on the screen through the appDelegate:
mpViewController = [[MainPageViewController alloc] init];
[window addSubview:[mpViewController view]];
[window makeKeyAndVisible];
The UIView contains more objects, such as UIButtons, that are visible. I am assuming because they are added as a subview. But I can't work out how to add the CGGradient as a subview? Does it need to be? Is there another reason CGGradient is not visible?
I also don't get the functionality on the UIButtons. I guess that is because of where I have added the UIButtons to the view. Do the buttons need to be added in the UIViewController or the appDelegate to have functionality. Sorry to ask what would seem like simple questions but I am trying to accomplish the programming without the Interface Builder and material on that is scarce. If anyone could point me in the right direction on both these problems I would really appreciate it.
Thanks!
The functionality on the buttons was lost because the frame was too large but the buttons were still visible because the background was clearColor

Copying the drawn contents of one UIView to another

I'd like to take a UITextView and allow the user to enter text into it and then trigger a copy of the contents onto a quartz bitmap context. Does anyone know how I can perform this copy action? Should I override the drawRect method and call [super drawRect] and then take the resulting context and copy it? If so, does anyone have any reference to sample code to copy from one context to another?
Update: from reading the link in the answer below, I put together this much to attempt to copy my UIView contents into a bitmap context, but something is still not right. I get my contents mirrored across the X axis (i.e. upside down). I tried using CGContextScaleCTM() but that seems to have no effect.
I've verified that the created UIImage from the first four lines do properly create a UIImage that isn't strangely rotated/flipped, so there is something I'm doing wrong with the later calls.
// copy contents to bitmap context
UIGraphicsBeginImageContext(mTextView.bounds.size);
[mTextView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage* image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self setNeedsDisplay];
// render the created image to the bitmap context
CGImageRef cgImage = [image CGImage];
CGContextScaleCTM(mContext, 1.0, -1.0); // doesn't seem to change result
CGContextDrawImage(mContext, CGRectMake(
mTextView.frame.origin.x,
mTextView.frame.origin.y,
[image size].width, [image size].height), cgImage);
Any suggestions?
Here is the code I used to get a UIImage of UIView:
#implementation UIView (Sreenshot)
- (UIImage *)screenshot{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
/* iOS 7 */
BOOL visible = !self.hidden && self.superview;
CGFloat alpha = self.alpha;
BOOL animating = self.layer.animationKeys != nil;
BOOL success = YES;
if ([self respondsToSelector:#selector(drawViewHierarchyInRect:afterScreenUpdates:)]){
//only works when visible
if (!animating && alpha == 1 && visible) {
success = [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
}else{
self.alpha = 1;
success = [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
self.alpha = alpha;
}
}
if(!success){ /* iOS 6 */
self.alpha = 1;
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
self.alpha = alpha;
}
UIImage* img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
#end
You can use in iOS 7 and later:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates

Resources