Need help for pushing a view in UIScrollView / UIPageControl on storyboard

谁说我不能喝 提交于 2019-12-05 07:21:26

问题


I'm trying to implement UIScrollView with UIPageControl on storyboard to display multiple views. All the examples, tutorials or informations I found are using xib and not storyboard. I tried to adapt Apple's sample code but I'm missing something. As you can see, it is quite simple.

However, either I get an error in ContentController.m -[NSNull view]: unrecognized selector sent to instance 0x129acd8 when I test if (controller.view.superview == nil) where I push the view (see below after the first half of code).

Or only the UIScrollView and UIPageControl are working and I am not able to push the view in the UIScrollView:

I know it's a simple question, however I spent many hours on this and any help will be appreciated.

I post the full code as it may help someone when resolved like a kind of tutorial. I will correct the code if/when needed according to contributions.

Thanks in advance

AppDelegate.h

// Nothing special as I don't want to manage it through application delegate
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

AppDelegate.m

// Nothing special as I don't want to manage it through application delegate
#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application { }

- (void)applicationDidEnterBackground:(UIApplication *)application { }

- (void)applicationWillEnterForeground:(UIApplication *)application { }

- (void)applicationDidBecomeActive:(UIApplication *)application { }

- (void)applicationWillTerminate:(UIApplication *)application { }

@end

ContentController.h

// From Apple's PageControl sample code
#import <UIKit/UIKit.h>

@interface ContentController : UIViewController <UIScrollViewDelegate> {
    UIScrollView *scrollView;
    UIPageControl *pageControl;
    NSMutableArray *viewControllers;
    BOOL pageControlUsed;
}

@property (nonatomic, retain) IBOutlet UIScrollView *scrollView;
@property (nonatomic, retain) IBOutlet UIPageControl *pageControl;
@property (nonatomic, retain) NSMutableArray *viewControllers;

- (IBAction)changePage:(id)sender;

@end

ContentController.m

// From Apple's PageControl sample code

#import "AppDelegate.h"
#import "ContentController.h"
#import "MyViewController.h"

// Private methods
@interface ContentController (PrivateMethods)
- (void)loadScrollViewWithPage:(int)page;
- (void)scrollViewDidScroll:(UIScrollView *)sender;
@end

@implementation ContentController

@synthesize scrollView, pageControl, viewControllers;

static NSUInteger kNumberOfPages = 6;

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    self.navigationController.navigationBar.hidden=YES;

    // view controllers are created lazily in the meantime, load the array with
    // placeholders which will be replaced on demand
    NSMutableArray *controllers = [[NSMutableArray alloc] init];
    for (unsigned i = 0; i < kNumberOfPages; i++) {
        [controllers addObject:[NSNull null]];
    }
    self.viewControllers = controllers;

    // a page is the width of the scroll view
    scrollView.pagingEnabled = YES;
    scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * kNumberOfPages, scrollView.frame.size.height);
    scrollView.showsHorizontalScrollIndicator = NO;
    scrollView.showsVerticalScrollIndicator = NO;
    scrollView.scrollsToTop = NO;
    scrollView.delegate = self;

    pageControl.numberOfPages = kNumberOfPages;
    pageControl.currentPage = 0;

    // pages are created on demand
    // load the visible page
    // load the page on either side to avoid flashes when the user starts scrolling
    [self loadScrollViewWithPage:0];
    [self loadScrollViewWithPage:1];
}

- (void)viewDidUnload {
    [super viewDidUnload];

    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    self.pageControl=nil;
    self.pageControl = nil;
    self.viewControllers = nil;
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc that aren't in use.
}

- (void)loadScrollViewWithPage:(int)page {
    if (page < 0)
        return;
    if (page >= kNumberOfPages)
        return;

    // replace the placeholder if necessary
    MyViewController *controller = [viewControllers objectAtIndex:page];

    if ((NSNull *)controller == [NSNull null]) // <== *** HERE SEEMS TO BE THE ERROR ***
    {
        controller = [[MyViewController alloc] initWithPageNumber:page
                                                  initWithNibName:nil
                                                           bundle:nil];

        [viewControllers replaceObjectAtIndex:page withObject:controller];
    }

    // add the controller's view to the scroll view
    if (controller.view.superview == nil)
    {
        CGRect frame = scrollView.frame;
        frame.origin.x = frame.size.width * page;
        frame.origin.y = 0;
        controller.view.frame = frame;
        [scrollView addSubview:controller.view];
    }
}

- (void)scrollViewDidScroll:(UIScrollView *)sender {
    // We don't want a "feedback loop" between the UIPageControl and the scroll delegate in
    // which a scroll event generated from the user hitting the page control triggers updates from
    // the delegate method. We use a boolean to disable the delegate logic when the page control is used.
    if (pageControlUsed) {
        // do nothing - the scroll was initiated from the page control, not the user dragging
        return;
    }

    // Switch the indicator when more than 50% of the previous/next page is visible
    CGFloat pageWidth = scrollView.frame.size.width;
    int page = floor((scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    pageControl.currentPage = page;

    // load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];

    // A possible optimization would be to unload the views+controllers which are no longer visible
}

// At the begin of scroll dragging, reset the boolean used when scrolls originate from the UIPageControl
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    pageControlUsed = NO;
}

- (IBAction)changePage:(id)sender {
    int page = pageControl.currentPage;

    // load the visible page and the page on either side of it (to avoid flashes when the user starts scrolling)
    [self loadScrollViewWithPage:page - 1];
    [self loadScrollViewWithPage:page];
    [self loadScrollViewWithPage:page + 1];

    // update the scroll view to the appropriate page
    CGRect frame = scrollView.frame;
    frame.origin.x = frame.size.width * page;
    frame.origin.y = 0;
    [scrollView scrollRectToVisible:frame animated:YES];

    // Set the boolean used when scrolls originate from the UIPageControl. See scrollViewDidScroll: above.
    pageControlUsed = YES;
}

@end

MyViewController.h

#import <UIKit/UIKit.h>

@interface MyViewController : UIViewController {
    UILabel *pageNumberLabel;
    int pageNumber;
}

@property (nonatomic, retain) IBOutlet UILabel *pageNumberLabel;

- (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

@end

MyViewController.m

#import "MyViewController.h"

@implementation MyViewController

@synthesize pageNumberLabel;

- (void)loadView {
}

- (void)viewDidLoad {
    [super viewDidLoad];

    // Set the label and background color when the view has finished loading
    pageNumberLabel.text = [NSString stringWithFormat:@"Page %d", pageNumber + 1];
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

// Load the view nib and initialize the pageNumber ivar
// classic initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
// class adapted to init the page number
// /!\ be careful, I'm not sure it's working properly
- (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    pageNumber = page;
    NSLog(@"pageNumber = %i", pageNumber);
}

- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}

@end

回答1:


The unrecognized selector error is happening because you are redeclaring your local variable "controller" within the if statement in loadScrollViewWithPage: Because you are doing so, the new controller variable only exists within the scope of the if statement, and when you exit the if statement the controller variable outside that scope is still NSNull.

You can fix by removing the variable definition inside the if statement:

if ((NSNull *)controller == [NSNull null])
{
    controller = [self.storyboard instantiateViewControllerWithIdentifier:@"MVC"];
    [controller initWithPageNumber:page];

    [viewControllers replaceObjectAtIndex:page withObject:controller];
}



回答2:


So I've been having exactly the same problem you were having but I just did something that might have worked just fine.

Short answer is an addition to Jonkroll's answer about initiating the controller inside the if statement. For some reason I can't explain the empty placeholder is not null so the if statement doesn't execute as you pointed out in your code. So what I did was modify the if and add an else to that same if and load the ViewController from the storyboard.

if ((NSNull *)controller == [NSNull null]) {
        controller = [self.storyboard instantiateViewControllerWithIdentifier:@"MVC"];        
        [viewControllers replaceObjectAtIndex:page withObject:[controller initWithPageNumber:page]];
}
else {
        controller = [self.storyboard instantiateViewControllerWithIdentifier:@"MVC"];        
        [viewControllers replaceObjectAtIndex:page withObject:[controller initWithPageNumber:page]];
}

The rest of my code is exactly as yours. I know is not the best code ever and I might be ok by just initiating the ViewController without an if/else statement all together but I don't have the answer as to why this is needed or worst why is not actually working as I expect. If anyone knows please share.




回答3:


First of all, thanx for this thread, it indeed served as a kind of a tutorial or a reference to me.

I did everything almost exactly like you did, except in content controller i put following code (based on @jonkroll's answer):

    if ((NSNull *)controller == [NSNull null]){
    controller = [[self.storyboard instantiateViewControllerWithIdentifier:@"MyViewController"] initWithPageNumber:page initWithNibName:nil bundle:nil];
    [viewControllers replaceObjectAtIndex:page withObject:controller];
}

MyViewController (has different name in my case, but it's of no relevance) inherits from UITableViewController, because it was kind of stuff i needed. My initWithPageNumber method looks like this:

    - (id)initWithPageNumber:(int)page initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
pageNumber = page;
NSLog(@"PageNumber = %i", pageNumber);
return self;

}

Added return.self, notice that.

One, last thing is, i embedded ContentController in Navigation controller, via editor->embed in->navigation controller.

That's pretty much everything. Later, i even managed to get navigation from MyViewController to another view and everything works fine.

If you need further help, you are free to ask here.

Regards.




回答4:


I had the same problem with you when I tried to follow the sample code of UIPageController from Apple Documents. For I did my MyViewController programmatically, so I directly use the following code

if ((NSNull *)controller == [NSNull null])
{
  MyViewController * controller = [[MyViewController alloc]init];
  [viewControllers replaceObjectAtIndex:page withObject:controller];
}

And I changed nothing in MyViewController.



来源:https://stackoverflow.com/questions/8714394/need-help-for-pushing-a-view-in-uiscrollview-uipagecontrol-on-storyboard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!