需要帮助在故事板上的 UIScrollView / UIPageControl 中推送视图

Posted

技术标签:

【中文标题】需要帮助在故事板上的 UIScrollView / UIPageControl 中推送视图【英文标题】:Need help for pushing a view in UIScrollView / UIPageControl on storyboard 【发布时间】:2012-01-03 15:17:38 【问题描述】:

我正在尝试在情节提要上使用 UIPageControl 实现 UIScrollView 以显示多个视图。我发现的所有示例、教程或信息都使用 xib 而不是故事板。我试图改编 Apple 的示例代码,但我遗漏了一些东西。如您所见,这很简单。

但是,当我测试 if (controller.view.superview == nil) 我推送视图的地方(见下文前半部分代码)。

或者只有 UIScrollView 和 UIPageControl 工作,我无法在 UIScrollView 中推送视图:

我知道这是一个简单的问题,但是我花了很多时间来解决这个问题,我们将不胜感激。

我发布了完整的代码,因为当它像一种教程一样解决时可能会对某人有所帮助。如果/需要时,我会根据贡献更正代码。

提前致谢

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

【问题讨论】:

如果有人知道教程的链接使用故事板,我将不胜感激。 这是一个很好的教程:raywenderlich.com/10518/… 【参考方案1】:

发生无法识别的选择器错误是因为您在 loadScrollViewWithPage 的 if 语句中重新声明了局部变量“控制器”:因为您这样做了,所以新的控制器变量仅存在于 if 语句的范围内,并且当您退出时if 语句该范围之外的控制器变量仍然是 NSNull。

您可以通过删除 if 语句中的变量定义来修复:

if ((NSNull *)controller == [NSNull null])

    controller = [self.storyboard instantiateViewControllerWithIdentifier:@"MVC"];
    [controller initWithPageNumber:page];

    [viewControllers replaceObjectAtIndex:page withObject:controller];

【讨论】:

感谢您的宝贵时间和回答。但是,我很确定错误不是来自这里。如果控制器变量为空,则在 if 语句中定义控制器变量。退出 if 语句时,它不再为空。 MyViewController *controller = [viewControllers objectAtIndex:page]; 中对变量的第一次引用并不是真正的声明(如果我错了,请纠正我)。此语句在 Apple 示例代码中运行良好。再次感谢。 仔细检查 Apple PageControl 示例中的代码:developer.apple.com/library/ios/#samplecode/PageControl/… 您会看到它们没有在 if 语句中声明控制器变量。当您这样做时,您将覆盖 if 语句范围内的现有变量。 (是的,以MyViewController * 开头的变量名确实构成声明) 是的,我仔细检查过,你是对的。谢谢你。但是,我仍然无法使用 Storyboard 使其工作。我必须努力获得更多经验。我暂时放弃了,因为我在这上面花了太多时间。我将使用 XIB 管理它。再次感谢。【参考方案2】:

所以我遇到了和你一样的问题,但我只是做了一些可能效果很好的事情。

简短的回答是对 Jonkroll 关于在 if 语句中启动控制器的回答的补充。出于某种原因,我无法解释空占位符不为空,因此 if 语句不会像您在代码中指出的那样执行。所以我所做的是修改 if 并将 else 添加到相同的 if 并从情节提要加载 ViewController。

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]];

我的其余代码与您的完全相同。我知道这不是有史以来最好的代码,我可能只需在没有 if/else 语句的情况下启动 ViewController 就可以了,但我不知道为什么需要这样做,或者最糟糕的是为什么实际上没有按我预期的那样工作.如果有人知道,请分享。

【讨论】:

顺便说一句,我知道这是很糟糕的编程,我可以摆脱 if 语句。实际发生的一件事是每次滚动页面时内存使用量都会达到最高值,并且我会收到导致崩溃的内存警告。如果有人知道如何将它与情节提要一起使用,我会全力以赴。【参考方案3】:

首先,感谢这个帖子,它确实是一种教程或对我的参考。

我所做的一切几乎和你一样,除了在内容控制器中我输入了以下代码(基于@jonkroll 的回答):

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

MyViewController(在我的例子中具有不同的名称,但没有相关性)继承自 UITableViewController,因为它是我需要的东西。 我的 initWithPageNumber 方法如下所示:

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

添加了return.self,注意这一点。

最后一件事是,我通过编辑器->嵌入->导航控制器将 ContentController 嵌入到导航控制器中。

这几乎就是一切。后来,我什至设法从 MyViewController 导航到另一个视图,一切正常。

如果您需要进一步的帮助,您可以在这里提问。

问候。

【讨论】:

【参考方案4】:

当我尝试遵循 Apple Documents 中的 UIPageController 示例代码时,我遇到了同样的问题。因为我以编程方式完成了 MyViewController,所以我直接使用以下代码

if ((NSNull *)controller == [NSNull null])

  MyViewController * controller = [[MyViewController alloc]init];
  [viewControllers replaceObjectAtIndex:page withObject:controller];

我在 MyViewController 中什么也没做。

【讨论】:

以上是关于需要帮助在故事板上的 UIScrollView / UIPageControl 中推送视图的主要内容,如果未能解决你的问题,请参考以下文章

UIScrollView 将元素从它们的设置位置移动

Xcode 故事板上的并排约束

UIButton 不在故事板上的同一个地方

故事板上的 ZXing 图书馆

ios 5故事板上的恢复标识符

iOS - 我的 iPad 故事板上的详细视图未填充