UIPageViewController black screen on iPad when loaded in landscape - objective-c

I'm developing a UIPageViewController based app which works both on iPhone and iPad. With the iPhone there are no problems, since the spine location of the controller is always set to UIPageViewControllerSpineLocationMin. In the iPad however, I need to set the spine location to UIPageViewControllerSpineLocationMid when the device orientation is set to Landscape.
I have a strange issue only when i Load the controller in Landscape orientation, the following video will explain the situation..
YouTubeVideo
As you can see, everything works perfectly when i load the controller in portrait and also after i rotate the device when i load in landscape.
I really can't figure out where is the problem. Here is the code I use to load the controllers:
if (chapter.controllers) {
currentPage = [chapter bookPageForCharIndex:location];
//Load the correct controller for the bookmark page
NSArray *array = nil;
if (currentPage >= 0 && currentPage < chapter.controllers.count) {
if (UIDeviceOrientationIsLandscape([[UIDevice currentDevice]orientation]) && UI_USER_INTERFACE_IDIOM()==UIUserInterfaceIdiomPad) {
[self setDoubleSided:YES];
if (chapter.controllers.count > currentPage+1) {
array = [[NSArray alloc]initWithObjects:[chapter.controllers objectAtIndex:currentPage],[chapter.controllers objectAtIndex:currentPage+1],nil];
}
else {
array = [[NSArray alloc]initWithObjects:[chapter.controllers objectAtIndex:currentPage],[[[LSBookPageViewController alloc]init]autorelease],nil];
}
}
else {
array = [[NSArray alloc]initWithObjects:[chapter.controllers objectAtIndex:currentPage],nil];
}
}
else {
return;
}
[self playSoundsOfChapter:chapter];
if (isRestarting) {
[self setViewControllers:array
direction:UIPageViewControllerNavigationDirectionReverse
animated:YES
completion:nil];
isRestarting = NO;
}
else {
[self setViewControllers:array
direction:UIPageViewControllerNavigationDirectionForward
animated:YES
completion:nil];
}
[array release];
}

I think the error is in the first else clause. You should set a breakpoint after checking for device orientation and device, and step through the code. I would guess that your array comes back empty.

Related

iOS Pull To Refresh Not Working on Specific Devices like iPhone X

I am using pull to refresh for usual data refresh on a tableview.
Here is my code
-(void)addUIRefreshControl{
//Instantiate and Configure Refresh Control
self.refreshControl = [[UIRefreshControl alloc] init]; // initialize refresh control
self.refreshControl.tintColor = [UIColor appBaseColor]; // set tint color
[self.refreshControl addTarget:self
action:#selector(refreshAPI)
forControlEvents:UIControlEventValueChanged]; // add target
[self.tableView addSubview:self.refreshControl]; // add the refresh control
}
-(void)refreshAPI{
if ([APP_DELEGATE isNetAvailable]) {
[self fetchAPI];
}else{
if (self.refreshControl) {
[self.refreshControl endRefreshing]; //end refreshing
}
}
}
Everything works fine on iOS Devices & Simulator except for iPhone X. Can anyone suggest what am I doing wrong?
Thanks
Is your app iOS 10 & newer only? If so, you should be using your commented out line:
- (void)addUIRefreshControl{
UIRefreshControl * refreshControl = [[UIRefreshControl alloc] init]; // initialize refresh control
refreshControl.tintColor = [UIColor appBaseColor]; // set tint color
refreshControl addTarget:self
action:#selector(refreshAPI)
forControlEvents:UIControlEventValueChanged]; // add target
if (self.tableView != NULL) {
// tableView will have a strong reference (and retain) refreshControl ; no need to have it be its own property
self.tableView.refreshControl = refreshControl
} else {
NSLog(#"suprise! self.tableView is null");
}
}
And you should always call "endRefreshing" when the value changes (or updates). Before, you were calling only if isNetAvailable was false.
-(void)refreshAPI{
if ([APP_DELEGATE isNetAvailable]) {
[self fetchAPI];
}
[self.tableView.refreshControl endRefreshing]; //end refreshing
}

using two finger pinch to zoom in collectionView Xcode

I have an app that has a collectionView inside a scrollView. I am using the following code to be able to zoom in and pan around.
-(void) viewDidLoad {
[super viewDidLoad];
twoFingerPinchA = [[UIPinchGestureRecognizer alloc]
initWithTarget:self
action:#selector(twoFingerPinch:)]
;
[self.view addGestureRecognizer:(twoFingerPinchA)];
}
-(void)twoFingerPinch:(UIPinchGestureRecognizer *)recognizer{
CGAffineTransform transform = CGAffineTransformMakeScale(twoFingerPinchA.scale, twoFingerPinchA.scale);
if (twoFingerPinchA.scale < 1) {
twoFingerPinchA.scale = 1;
}
if (twoFingerPinchA.scale >2) {
twoFingerPinchA.scale = 2;
}
self.scrolling.transform = transform;
}
This works well except when I am zoomed in and remove my fingers. When I put my two fingers back on the screen to zoom out or in again the view returns to not zoomed in. How can I get the view to stay zoomed in when I replace my two fingers on the screen to rezoom.
I have tried capturing the twoFingerPinchA.scale value which I can do. But I don't know how to set the initial value of the twoFingerPinchA.scale to that value as it alway returns to 1.
Any ideas?
So I am now trying to detect when two fingers touch the screen and when the two finger touch stops so I can save and insert the value of the twoFingerPinchA.scale.
However No matter what I put in I can't seem to detect the touches.
I have my controllers regular view, then I have a scroll view in it and then a collectionView inside of it.
I have enabled interaction and multiple touch for all views and tried to detect the touches in all view but don't get a call back. Here is my code.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"Event:%#",event);
// I don't even get this call back as a touch beginning on any view.
if ([[event touchesForView:self.view] count] >0) {
NSLog(#"touches");
}
}
So I figured this out.
First I used this code
-(void) viewWillAppear:(BOOL)animated{
NSString *pinchString = #"1.0";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:pinchString forKey:#"pinchValue"];
}
//this was to make sure that the pinchValue key had a value when the app is first installed.
then I put this
switch ([twoFingerPinchA state]) {
case UIGestureRecognizerStatePossible:
{
}
break;
case UIGestureRecognizerStateBegan:
{
NSString *inPinch = [[NSUserDefaults standardUserDefaults] objectForKey:#"pinchValue"];
float Pinch = [inPinch floatValue];
twoFingerPinchA.scale = Pinch;
}
break;
case UIGestureRecognizerStateChanged:
{
}
break;
case UIGestureRecognizerStateEnded:
{
NSString *pinchString = [NSString stringWithFormat:#"%f",twoFingerPinchA.scale];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:pinchString forKey:#"pinchValue"];
}
break;
case UIGestureRecognizerStateCancelled:
{
}
break;
case UIGestureRecognizerStateFailed:
{
}
break;
}
in my
-(void)twoFingerPinch:(UIPinchGestureRecognizer *)recognizer{}
This stores the value of the twoFingerPinchA.scale in usersDefaults when the pinch ends
and
sets the twoFingerPinchA.scales to that stored value when it begins again. From there it adjust up and down /in and out according to this value.
Works great

Can't access a UIWebView apparently coming from storyboard

There's a view with one UIWebview in Portrait and two smaller UIWebViews in landscape - In landscape the two views represent the pages of a book and there's some pageview stuff going on. I can not determine where the second UIWebView is coming from. When the superview is loaded in landscape I can only access one of the the view on the left. The view on the right displays perfectly with the correct data, but I can't access it from the code until something causes the screen to redraw such as the page being "turned" or the orientation changing. Does this sound familiar to anyone?
Also, my NSLog function isn't working anymore and I have no idea where to click to make it come back. Please help.
Edit
Here's some code
// this is the function which is not accessing the view.
-(void) setCurrentTextSize:(int)cts {
for(int pageViewIndex = 0; pageViewIndex < [arrayOfReaderViewControllers count]; pageViewIndex++) {
readerViewController *r = [self arrayOfReaderViewControllers:pageViewIndex];
[r setCurrentTextSize:cts];
[r webViewDidFinishLoad:r.webView];
}
}
//this function populates arrayOfReaderViewControllers
-(void)initializeArrayOfReaderViewControllers {
readerViewController *vcLeft;
if([self.delegate isFirstLoad]) {
//we don't care what happens here 'cause it's not being called.
} else {
vcLeft = [readerViewController new];
if ( lastPageNumber > currentPageNumber) {
currentPageNumber = lastPageNumber;
[arrayOfReaderViewControllers replaceObjectAtIndex:0 withObject:[self jumpToPageVC:vcLeft]];
if(self.isLandscape) {
vcLeft = [arrayOfReaderViewControllers objectAtIndex:0];
[arrayOfReaderViewControllers replaceObjectAtIndex:1 withObject:[self getNextPageViewController:vcLeft]];
}
}
}
[self.delegate doMoreInitialization];
}

iOS return landscape orientation when is Portrait

I am trying to control iAd size when starts from landscape or portrait. Problem is that device tells that is on landscape when is on portrait! How to handle it? Thank you
- (void)viewDidLoad
{
//iAd
adView =[[ADBannerView alloc] initWithFrame:CGRectZero];
adView.requiredContentSizeIdentifiers = [NSSet setWithObjects: ADBannerContentSizeIdentifierPortrait, ADBannerContentSizeIdentifierLandscape, nil];
adView.delegate = self;
if (UIInterfaceOrientationIsPortrait([UIDevice currentDevice].orientation)) {
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
} else {
adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
}
[self.view addSubview:adView];
[super viewDidLoad];
}
You almost always want to use [self interfaceOrientation] here.
Regarding why orientation doesn't work, note the docs:
The value of this property always returns 0 unless orientation notifications have been enabled by calling beginGeneratingDeviceOrientationNotifications.
Are you sure it's telling you it's on landspace or simply not Portrait, as you if is setup to only recognize Portrait. It's possible that the simulator is returning nil, as mentioned in this thread: UIDevice currentDevice's "orientation" always null
Are you using the simulator or a device?

Animation using array of images in sequence

I have an array of images which I want to animate by playing these images one after the other in a sequence. I want to repeat the whole loop several times. I am developing a game for iPad. Suggest to me a method to achieve this functionality in Objective-C with the Cocoa framework.
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"images.jpg"],
[UIImage imageNamed:#"images1.jpg"],
[UIImage imageNamed:#"images5.jpg"],
[UIImage imageNamed:#"index3.jpg"],
nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor = [UIColor purpleColor];
animationView.animationImages = animationArray;
animationView.animationDuration = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating];
[self.view addSubview:animationView];
[animationView release];
add your own images in the array.repeat Count 0 means infinite loop.You can give your own number also.
There are at least 3 ways to animate an array of images through a UIImageView. I'm adding 3 links to download sample code for the 3 possibilities.
The first one is the one that everyone knows. The other ones are less known.
- UIImageView.animationImages
Example Link
The problem of this one is that do not have Delegate to tell us in which moment the animation is finished. So, we can have problems if we want to display something after the animation.
In the same way, there is no possibility to kept the last image from the animation in the UIImageView automatically. If we combine both problems we can have a gap at the end of the animation if we want to kept the last frame on screen.
self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];
- CAKeyframeAnimation
Example Link
This way to animate works through CAAnimation. It have an easy delegate to use and we can know when the animation finish.
This is probably the best way to animate an array of images.
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"girlAnimation"];
}
Delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
// your code
}
}
- CADisplayLink
Example Link
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
This way to do it is really interesting and opens a lot of possibilities to manipulate what are we showing in screen.
DisplayLink getter:
- (CADisplayLink *)displayLink
{
if (!_displayLink)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(linkProgress)];
}
return _displayLink;
}
Methods:
- (void)animateImages
{
self.displayLink.frameInterval = 5;
self.frameNumber = 0;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
- (void)linkProgress
{
if (self.frameNumber > 16)
{
[self.displayLink invalidate];
self.displayLink = nil;
self.animationImageView.image = [UIImage imageNamed:#"lastImageName"];
self.imagesArray = nil;
return;
}
self.animationImageView.image = self.imagesArray[self.frameNumber++];
self.frameNumber++;
}
GENERAL PROBLEM:
Even though we have this 3 possibilities, if your animation is with a lot of big images, consider using a video instead. The usage of memory will decrease a lot.
A General problem you will face doing this is in the moment of the allocation of the images.
If you use [UIImage imageNamed:#"imageName"] you will have cahe problems.
From Apple:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
See the animationImages property of UIImageView. It’s hard to say if it fits your needs as you don’t give us details, but it’s a good start.
I have added a swift 3.0 extension for this
extension UIImageView {
func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = images[index]
}, completion: { value in
let idx = index == images.count-1 ? 0 : index+1
if idx == 0 {
completionHandler!()
} else {
self.animate(images: images, index: idx, completionHandler: completionHandler)
}
})
}
}
Best solution for me use CADisplayLink. UIImageView doesn't have completion block and you can't catch steps of animation. In my task i must changing background of view with image sequencer step by step. So CADisplayLink allows you handling steps and finishing animation. If we talk about usage of memory, i think best solution load images from bundle and delete array after finishing
ImageSequencer.h
typedef void (^Block)(void);
#protocol ImageSequencerDelegate;
#interface QSImageSequencer : UIImageView
#property (nonatomic, weak) id <ImageSequencerDelegate> delegate;
- (void)startAnimatingWithCompletionBlock:(Block)block;
#end
#protocol ImageSequencerDelegate <NSObject>
#optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
#end
ImageSequencer.m
- (instancetype)init {
if (self = [super init]) {
_imagesArray = [NSMutableArray array];
self.image = [self.imagesArray firstObject];
}
return self;
}
#pragma mark - Animation
- (void)startAnimating {
[self startAnimatingWithCompletionBlock:nil];
}
- (void)startAnimatingWithCompletionBlock:(Block)block {
self.frameNumber = 0;
[self setSuccessBlock:block];
self.displayLink.frameInterval = 5;
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStart)]) {
[self.delegate animationDidStart];
}
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
-(void)stopAnimating {
self.image = [self.imagesArray lastObject];
[self.displayLink invalidate];
[self setDisplayLink:nil];
Block block_ = [self successBlock];
if (block_) {
block_();
}
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStop)]) {
[self.delegate animationDidStop];
}
[self.imagesArray removeAllObjects];
}
- (void)animationProgress {
if (self.frameNumber >= self.imagesArray.count) {
[self stopAnimating];
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(didChangeImage:)]) {
[self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
}
self.image = self.imagesArray[self.frameNumber];
self.frameNumber++;
}
#pragma mark - Getters / Setters
- (CADisplayLink *)displayLink {
if (!_displayLink){
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animationProgress)];
}
return _displayLink;
}
- (NSMutableArray<UIImage *> *)imagesArray {
if (_imagesArray.count == 0) {
// get images from bundle and set to array
}
return _imagesArray;
}
#end
This is a simple and working code for animation./
-(void)move
{
[UIView animateWithDuration:5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}