Based on some earlier questions (ex: ios5 how to pass prepareForSegue: a UIImageView) I was wondering how smooth this technique is, say when doing a modal segue with animated = NO.
I can toss together a test but I imagine someone has already scoped out the performance and practical pitfalls of using this as a transition technique. I would be interested in hearing any details - for example, does the UIImageView animation restart or does it continue apace?
If you pass an animating image view to another controller, it looks like the animation continues apace. I can't see any "hitch" in the animation. Going forward with a modal presentation worked fine. I couldn't go back to the first controller, by just dismissing, I had to add the image view back to the view to make that work, and even then it only worked if I turned off auto layout (otherwise I get an error saying it can't install a constraint). So, to go back and forth with modals (with no animation) I had this in the first controller:
- (void)viewDidLoad {
[super viewDidLoad];
self.isFirst = YES;
NSMutableArray *imgArray = [NSMutableArray array];
for (int i = 1; i< 41; i++) {
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:#"New_PICT%04d.jpg",i]];
if (img) {
[imgArray addObject:img];
}
}
self.iv.animationImages = imgArray;
self.iv.animationDuration = 10;
[self.iv startAnimating];
}
-(void)viewDidAppear:(BOOL)animated {
if (self.isFirst) {
self.isFirst = NO;
}else{
self.iv.frame = CGRectMake(48, 48, 48, 48);
[self.view addSubview:self.iv];
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
SecondViewController *sec = segue.destinationViewController;
sec.iv = self.iv;
}
Related
I have a custom UIImagePickerController that works nicely, only I am facing one issue that I feel should be fairly simple - I just have yet to figure out the solution.
Upon touching my custom added "photo" button, I have it targeted to the build in takePicture method of the UIIPC. Here is my code
#interface CustomCameraController ()
#end
#implementation CustomCameraController {
CGFloat width, height;
}
- (instancetype)init {
if (self = [super init]) {
width = self.view.frame.size.width, height = self.view.frame.size.height;
self.allowsEditing = YES;
self.sourceType = UIImagePickerControllerSourceTypeCamera;
self.showsCameraControls = NO;
self.toolbarHidden = YES;
[self buildCameraOverlay];
}
return self;
}
- (void)buildCameraOverlay {
UIView *customOverlay = [UIView alloc] ...
// ... Custom overlay setup done here
_takePhoto = [[CustomButton alloc] initWithFrame:CGRectMake(0, 0, heightBottomBar*.5, heightBottomBar*.5)];
_takePhoto.center = CGPointMake(bottomBar.frame.size.width/2, bottomBar.frame.size.height/2);
[_takePhoto setImage:[UIImage imageNamed:#"camera button icon"] forState:UIControlStateNormal];
[_takePhoto addTarget:self action:#selector(takePicture) forControlEvents:UIControlEventTouchUpInside];
[bottomBar addSubview:_takePhoto];
// ...
self.cameraOverlayView = customOverlay;
}
This is done in my custom controller CustomCameraController init call.
The problem is, upon taking the picture via takePicture, the camera shutter goes off, everything works just fine, but the controller dismisses itself. I'm trying to figure out how to stop it from closing immediately after taking the picture, so I can A)present the taken picture, and B) give the user the option to choose the image or cancel and retake another one (returning to the camera)
If anyone knows why this happens or something that I am missing / doing incorrectly please let me know. I'm sure it's a simple answer - just can't seem to figure it out. Thanks!
The most common reason for such a weird behaviour is usually lack of delegate methods (for UIImagePickerController in this case) or their wrong implementation.
I started writing a project without really using the storyboard (as i was following a tutorial about creating an options menu for a game).
So i am trying to retroactively implement a way of moving between 2 UIViewControllers (MainGame and MatchScreen) that each has a UIView (MainMenu and VSComputer respectively).
Now in the MainMenu UIView I have created 4 buttons, with all their positions and settings, two of which are subviews - options and stats - that so appear and disappear when clicked on the current ViewController.
Now, the problem is, I need to somehow move from the main menu to the match screen using one of the other buttons named 'matchScreen'. I feel there must be something that i'm missing as no answer found in my research has worked for me yet.
I have embedded my main UIViewController with a navController (as suggested in other questions that i've seen) and then added a push segue to the 2nd ViewController. But i don't know how to make this apparent to my code. My code recognises nothing that i do in the storyboard it seems.
#interface JDTHMainMenu(){
JDTHOptionsScreen *theOptionScreen;
JDTHPlayerStats *thePlayerStatsScreen;
UIButton *optionScreenButton;
UIButton *matchScreenButton;
UIButton *deckScreenButton;
UIButton *statsScreenButton;
bool isPhone;
bool isOptionScreenOpen;
bool isPlayerStatsScreenOpen;
bool matchScreenButtonPressed;
UIView *tint;
int screenWidth;
int screenHeight;
AppData *appData;
}
So to be clear, JDTHMainMenu is a UIView in the JDTHViewController - the original, JDTHMatchScreen is the second ViewController and there is a UIView called JDTHVersusScreen. Im trying to go from one to the other. Also the JDTHAppDelegate only has return YES in the didFinishLaunching method. I feel i'm getting closer....but perhaps not...
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
appData = [AppData sharedData];
NSLog(#"Main Menu has loaded");
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
isPhone = YES;
} else {
isPhone = NO;
}
screenWidth = self.frame.size.width;
screenHeight = self.frame.size.height;
isOptionScreenOpen = NO;
isPlayerStatsScreenOpen = NO;
matchScreenButtonPressed = NO;
[self showOptionScreenButton];
[self showPlayerStatsScreenButton];
[self matchViewControllerButton:matchScreenButton];
[self deckViewerControllerButton:deckScreenButton];
}
return self;
}
-(IBAction)matchViewControllerButton:(UIButton *)sender{
CGRect rect;
UIFont *theFont;
if (isPhone == YES) {
rect = CGRectMake(38, 150, 134, 116);
theFont = [UIFont fontWithName:#"Zapfino" size:22];
} else {
rect = CGRectMake(275, 600, 200, 150);
theFont = [UIFont fontWithName:#"Zapfino" size:28];
}
matchScreenButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[matchScreenButton addTarget:self action:#selector(goToMatchView:sender:) forControlEvents:UIControlEventTouchDown];
[matchScreenButton.titleLabel setFont:theFont];
[matchScreenButton setTitle:#"VS Comp" forState:UIControlStateNormal];
matchScreenButton.frame = rect;
[matchScreenButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[matchScreenButton setBackgroundImage:[UIImage imageNamed:#"MainMenuOptionButton"] forState:UIControlStateNormal];
[self addSubview:matchScreenButton];
}
-(void)goToMatchView:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"pushToMatchView"]) {
JDTHMatchScreen *vc2 = (JDTHMatchScreen *)segue.destinationViewController;
vc2.name = self.textField.text;
}
}
A storyboard app has an entry in the info.plist file that tells the app that it's a storyboard app, and what the name of the storyboard is ("Main storyboard file base name" is the name of the key, and the value is the file name, without extension, of your storyboard). You would need to add this to your project to get it to recognize the storyboard. Also, non-storyboard apps usually have code in the app delegate to set up the initial controllers. This should be deleted for a storyboard app (the only code in the default template in didFinishLaunchingWithOptions: is "return YES"). It might be easier to just start a new storyboard based single view project and copy your files to it.
i've got a question regarding a UIScrollView with paging enabled that contains many UIView, each managed by an own UIViewController.
Right now there are about 20 to 30 UIViewControllers that COULD be contained in the UIScrollView. It's a catalog app on the iPad, and I started preloading all the views at the beginning, but with the amount of UIViewControllers getting bigger and bigger, that is not an option any more.
I'm looking for the perfect solution in terms of memory usage. It's no problem to reload the UIViewControllers when the ContentOffset of the ScrollView reaches a specific controller. And I think to nil the UIViewControllers when the ContentOffset tells me that the UIViewControllers is not needed any more isn't that hard as well.
What is the correct way to handle this? Is it enough to alloc the UIViewControllers when needed, putting them into a NSMutableDictionary or NSMutableArray and nil them when they are not needed any more? A little bit of help from someone already having done something similar would be great!
Thanks for your help!
I'm sure there are some good infinite scrolling classes out there, but if you were going to "roll your own", here is a minimalist bit of code that demonstrates the process of infinite scrolling, keeping the current, previous, and next pages in memory, but letting go of anything else. This assumes that:
you're doing horizontal scrolling and have turned on paging;
that you're using view controllers for the child views;
your child view controller class has a page property to keep track of what page it's for; and
you've made your view controller the delegate for your scroll view
Thus, it might look like:
- (void)viewDidLoad
{
[super viewDidLoad];
// my underlying model is just an array of strings, which I'll show on my child
// view; your model will be more elaborate, but I just want to illustrate the concept
self.objects = #[#"1", #"2", #"3", #"4", #"5", #"6", #"7", #"8", #"9"];
// set the `contentSize` for the scrollview
CGRect content = self.view.bounds;
content.size.width *= [self.objects count]; // make it wide enough to hold everything
self.scrollView.contentSize = content.size;
// set our current page and load the first pages (the first and the next pages)
self.currentPage = 0;
[self addChildPage:0 toScrollView:self.scrollView];
[self addChildPage:1 toScrollView:self.scrollView];
}
- (void)addChildPage:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
// create the child controller
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"child"];
// set whatever properties you need to in order for it to present its information correctly
controller.text = self.objects[page];
controller.page = page;
// now do the stuff to add it to the right place in the scrollview
CGRect frame = self.view.bounds;
frame.origin.x = frame.size.width * page;
controller.view.frame = frame;
[self addChildViewController:controller]; // containment call for adding child view controller
[scrollView addSubview:controller.view];
[controller didMoveToParentViewController:self]; // containment call when done adding child
}
- (ChildViewController *)childControllerForPage:(NSInteger)page
{
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page == page)
return controller;
}
return nil;
}
- (void)addChildIfNecessary:(NSInteger)page toScrollView:(UIScrollView *)scrollView
{
if (page < 0 || page >= [self.objects count])
return;
ChildViewController *controller = [self childControllerForPage:page];
if (controller == nil)
[self addChildPage:page toScrollView:scrollView];
}
- (void)removeChildController:(UIViewController *)controller
{
[controller willMoveToParentViewController:nil]; // containment call before removing child
[controller.view removeFromSuperview];
[controller removeFromParentViewController]; // containment call to remove child
}
- (void)updateChildrenViewsForPage:(NSInteger)page forScrollView:(UIScrollView *)scrollView
{
if (page == self.currentPage)
return;
// add child pages as necessary
[self addChildIfNecessary:page toScrollView:scrollView];
[self addChildIfNecessary:(page-1) toScrollView:scrollView];
[self addChildIfNecessary:(page+1) toScrollView:scrollView];
// find any pages that need removing
NSMutableArray *pagesToRemove = [NSMutableArray array];
for (ChildViewController *controller in self.childViewControllers)
{
if (controller.page < (page - 1) ||
controller.page > (page + 1))
{
[pagesToRemove addObject:controller];
}
}
// remove them if they need removing
for (UIViewController *controller in pagesToRemove)
{
[self removeChildController:controller];
}
// update our "current page" index
self.currentPage = page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSInteger page = scrollView.contentOffset.x / scrollView.frame.size.width + 0.5;
[self updateChildrenViewsForPage:page forScrollView:scrollView];
}
This demonstrates the appropriate custom container calls and the handling of the scroll events. I hope this helps.
In my app I habe a view controller that calls several views. All these views are UIViews. That works fine, but not in every case. One of the views that are called has some labels, textfields and two UITextViews. Everything is shown correctly but the UITextViews. The view is called in that way:
[[self view] addSubview:tasteView];
//tasteView = [[TasteView alloc] init];
[self setCurrentView:tasteView];
I call the init method of the view to display the UITextViews:
EDIT: After a comment of Phillip Mills this was slightly changed! Init isn't called anymore.
- (id)init
{
if (self)
{
[tv1 setNeedsDisplay];
CGRect frame = tv1.frame;
frame.size.height += 1;
tv1.frame = frame;
}
return self;
}
As I saw that setNeedsDisplay had no effect, I changed the size of the corresponsing frame to force a redraw. Unfortunately that had no effect, too.
Btw, the view is initially loaded in the viewDidLoad of the view controller:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setCurrentView:placeholder];
[self configureView];
wineryView = [self loadWineryView];
wineView = [self loadWineView];
tasteView = [self loadTasteView];
}
A method for loading the views looks like this:
- (UIView *) loadTasteView
{
NSArray *nibViews = [[NSBundle mainBundle] loadNibNamed:#"TasteView" owner:self options:nil];
UIView *tView;
for (id view in nibViews)
{
if ([view isKindOfClass:[TasteView class]])
{
tView = (TasteView*) view;
}
}
return tView;
}
I do not know why those UITextViews are not shown. Did I forget something? To show really everything, here are the connections that I made in InterfaceBuilder:
Does anyone know what I did wrong and can help me?
I think your initial code should be like this :
tasteView = [[TasteView alloc] init];
[[self view] addSubview:tasteView];
[self setCurrentView:tasteView];
addSubView after it is allocated
Hope it helps you
If you are creating the view in code (your first sample), alloc and init the view before trying to add it as a subview.
If you're loading it from another nib (last code section), you still need to add it to the view hierarchy.
I have an app the loaded many view controllers in a scroll view depending on the number of objects the user has in a tableview. So when I flip between the tableview and the scroll view, the number of view controllers in the scroll view changes according to how many objects the user has in the tableview.
I use the code in Apple's PageControl sample code to build the scroll view with many view controllers inside it, after some modification of course.
- (void)loadScrollViewWithPage:(int)page
{
if (page < 0) return;
if (page >= kNumberOfPages) return;
// replace the placeholder if necessary
MainViewController *countdownController = [viewControllers objectAtIndex:page];
if ((NSNull *)countdownController == [NSNull null])
{
id occasion = [eventsArray objectAtIndex:page];
countdownController = [[MainViewController alloc] initWithPageNumber:page];
[countdownController setOccasion:occasion];
[viewControllers replaceObjectAtIndex:page withObject:countdownController];
[countdownController release];
}
// add the controller's view to the scroll view
if (nil == countdownController.view.superview)
{
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
countdownController.view.frame = frame;
[scrollView addSubview:countdownController.view];
}
}
The problem is the number of living view controllers (MainViewController here) keeps increasing when I flip between the table view and the scroll view (according to Instruments) even though I didn't add any new objects which causes memory problems of course.
I tried so many things in viewWillDisappear of the scroll view like:
- (void) viewWillDisappear:(BOOL)animated
{
//test unloading all views
//Remove all subviews
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
//[[scrollView subviews] makeObjectsPerformSelector:#selector(release)];
//[viewControllers removeAllObjects];
for (unsigned m = 0; m < [viewControllers count]; m++)
{
//[[viewControllers objectAtIndex:m] makeObjectsPerformSelector:#selector(release)];
[viewControllers removeObjectAtIndex:m];
}
}
But it didn't work.
Here is a recording of how the app works youtube.com/watch?v=5W8v_smZSog
And this is the viewWillAppear method of the scroll view:
- (void)viewWillAppear:(BOOL)animated
{
eventsArray = [[NSMutableArray alloc] init];
kNumberOfPages = [self.dataModel occasionCount];
//update the eventsArray from the dataModel
//Fill in the events Array with occasions form the data model
for (unsigned r = 0; r < kNumberOfPages; r++)
{
Occasion* occasion = [self.dataModel occasionAtIndex:r];
[eventsArray insertObject:occasion atIndex:r];
}
// 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;
[controllers release];
// 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 = currentPage;
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
UPDATE: Video recording of Instruments http://www.youtube.com/watch?v=u1Rd2clvMQE&feature=youtube_gdata_player
And a screen shot showing the responsible caller:
Thank you.
This is for you if you don't want to use UIPageViewController (read my other answer).
The sample project is designed for a constant number of pages (kNumberOfPages). The scrollview content size and the size of the view controller array depends on the number of pages. The sample code set this up in awakeFromNib, which is called only once.
So in order to make this dynamic you could recreate the whole ContentController when the number of pages changes. You just need to add a property for the number of pages.
The other option would be to reset the scrollview and view controller array when the number of pages changes.
I'm assuming you have defined a property for the events:
#property(nonatomic,retain) NSArray* eventsArray;
You could then add a setter method like this:
-(void)setEventsArray:(NSArray *)eventsArray
{
if (eventsArray != _eventsArray) {
[_eventsArray release];
_eventsArray = [eventsArray retain];
NSUInteger eventCount = [eventsArray count];
//reset scrollview contentSize
scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * eventCount, scrollView.frame.size.height);
// reset content offset to zero
scrollView.contentOffset = CGPointZero;
//remove all subviews
[[scrollView subviews] makeObjectsPerformSelector:#selector(removeFromSuperview)];
pageControl.numberOfPages = eventCount;
// reset viewcontroller array
NSMutableArray *controllers = [[NSMutableArray alloc] init];
for (unsigned i = 0; i < eventCount; i++)
{
[controllers addObject:[NSNull null]];
}
self.viewControllers = controllers;
[controllers release];
[self loadScrollViewWithPage:0];
[self loadScrollViewWithPage:1];
}
}
You call this method from the table view controller at the time when the user switches to the scroll view.
Apple's PageControl sample code is 2 years old and you can consider it as deprecated because there is a new container view controller in iOS 5 that does all this: UIPageViewController.
You should really start using UIPageViewController, then you don't need that loadScrollViewWithPage method at all. It would be less code and more easy.
Take a look at the PhotoScroller sample code. It has been updated to take full advantage of UIPageViewController.
It doesn't look like you are implementing Apple's View Controller Containment pratices. It would make memory management that much easier and safer.
Plus, hoping that it might save you a lot of future headaches, there is already an open source project that does what you are describing (implementing a self-managing scrollview of an arbritary number of view controllers).
You might want to take a look at it: RHHorizontalSwipe.
The concept of a UIScrollView containing multiple UIViewController views sounds sketchy at best, that design does not sound good at all.
That being said, one potential issue could be this line:
if ((NSNull *)countdownController == [NSNull null])
You would be better off with something like this:
if (!countdownController || [countdownController isKindOfClass:[NSNull class]])
Also, you should call [super viewWillDisappear:animated] in your viewWillDisappear method.