I have been looking for tutorials regarding the setup of a UIPageViewController because somebody recommended not using my navigational controller but instead to use the PageView. I am using the swipe gesture left and right as well but I am yet to find a good tutorial online in regards to the setup of the UIPageViewController via storyboard (or at least majority storyboard).
I included a picture of what I am working with at the moment: http://imgur.com/a0iuFNW
Any help in regards to the setup of the PageViewController and then following controllers would be much appreciated!
Thank you for your help in advanced
I'm sure you can find some tutorials out there if you Google it, but this should give you a start trying your own implementation. I created a new project starting from the "Page-Base Application" template. You should study the code that gives you to try to understand what it does (along with reading Apple's documentation on UIPageViewController).
That project creates several objects. The RootViewController creates the page view controller and adds it as a child view controller. For my test app, I didn't change anything in this file other than changing the transition style of the page view controller to UIPageViewControllerTransitionStyleScroll (instead of page curl, but you should try the original value too to see what it looks like).
The ModelController class contains the model, which in your case would be an array of image names (or pieces of the name as I have). This class implements the page view controller data source methods that handle the turning of the pages. I changed the init method in that class to this,
- (id)init {
if (self = [super init]) {
_pageData = #[#"1", #"2", #"3", #"4", #"5", #"6"]; // my images are named img1.JPG, img2.JPG, etc. I will use these numbers to construct the path to my files in the bundle
}
return self;
}
So, you can see that the model is just an array of number strings (one number for each image). You might have to try something different depending on how your images are named. When you swipe between pages, the index into this array is either incremented or decremented, and the value in the array is passed to the DataViewController into its dataObject property.
The DataViewController is the controller that actually shows your content. It's the right controller in the storyboard. I modified its view by adding a navigation bar at the top so we can put in a title and button (to go to your tips view controllers), and replaced the UIView subview with a UIImageView that takes up the whole screen under the navigation bar. I made IBOutlets to the image view and the navigation bar. Here is the changed .h file for that controller,
#interface DataViewController : UIViewController
#property (strong, nonatomic) NSString *dataObject;
Finally, in the .m file, I made it look like this,
#interface DataViewController ()
#property (weak,nonatomic) IBOutlet UINavigationBar *bar;
#property (weak,nonatomic) IBOutlet UIImageView *iv;
#end
#implementation DataViewController
- (void)viewDidLoad{
[super viewDidLoad];
UINavigationItem *titleItem = self.bar.items[0];
titleItem.title = [NSString stringWithFormat:#"Course Guide %#",self.dataObject];
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:#"img%#", self.dataObject] ofType:#"JPG"];
self.iv.image = [UIImage imageWithContentsOfFile:imagePath];
}
-(void)dealloc {
NSLog(#"in dealloc");
}
I'm using initWithContentsOfFile: to create the images (rather than imageNamed: because it doesn't cache the images) based on the number that's passed in to the dataObject. I implemented dealloc to illustrate the fact that as you swipe back and forth through the pages, the controllers are deallocated, thus keeping your memory footprint small. If the transition style is page curl, the controllers are deallocated as soon as they go off screen, so there's never more than one in existence at a time. If the style is scroll, then there are 3 in existence at a time, because the page view controller creates the current one as well as the previous and next ones to make the loading faster.
As you can see, I've made only a few small changes to the code the template gives you, in order to display an array of images which should suit your purpose. A structure such as this for your app will be much more memory efficient than using a stack of controllers in a navigation controller.
Related
I'm working on a major update to one of my applications and trying to cut down memory usage and making it cleaner and faster. I was using Instruments to profile the app and I was looking at the UIImage allocations, I have about 10 when the app starts out (although one is a status bar icon? Dont know why thats included). When i open up my Settings view controller (which is in a split view controller on iPad) it has basically an image with every table view cell, which is a lot. Presenting it for the first time adds 42 images. when I dismiss this view controller, there is still 52 images, when there should be only 10 now. If I present the controller again, there are now 91 images. This keeps going up and up. Instruments doesn't say there is a leak and I can't figure out what is happening. Each cell sets an image like
cell.imageView.image = [UIImage imageNamed:#"Help-Icon"];
How can I figure out why these images are not being released?
EDIT:
I think Its deallocating the images now. I changed from setting the imageView image directly to setImage:, so a table cell looks like this now:
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = NSLocalizedString(#"Homepage", #"Homepage");
UIImage *cellImage = [UIImage imageNamed:#"Homepage-Icon"];
[cell.imageView setImage:cellImage];
[MLThemeManager customizeTableViewCell:cell];
return cell;
MLThemeManager is a singleton that using a theme class to set properties of the cell to a theme, like the text label color, highlighted color, detail text label color, and background color.
The possible reason here is because of your Settings view controller was not deallocated (not released from memory). No matter how many images are in that view controller when view controller release from memory it will deallocates (remove) all image object from memory.
How to check your view controller is de allocated (release) or not?
Steps:
From Xcode long press on Run button. You get a small popup, Select Profile from that. And you can see new icon replaces the Run icon. (Similarly you can change it Run button to run the application).
Click on this button will start to profile your application.
Select Allocations section in Instruments window. There is a textfield above the allocation listing view. Click on that field and write your view controller name. (In your case SettingViewController).
You can see only filter result related to that key word you just type. Now go to Simulator -> Simulate your flow. Pop back from your view controller and check in Instruments that after leaving that view controller if it's still in the list. If it is there in a list than your view controller is not release from memory and each time you open that view controller will increase memory and never release until application is running.
How we can de allocate view controller?
I'll try to explain some way which I know to de allocated controller.
[ 1 ] Override dealloc method in your view controller and release objects in that. Mainly mutable variable and delegates. Put a debug breakpoint on -dealloc method and make sure it is called when you left that controller.
Note: If you have create global variable for class like UIPickerView, UIPopoverController, UIImagePickerController etc. and set delegates for that than these delegates must be nil in -dealloc method.
e.g.
Objective C code:
//---------------------------------------------------------------
#pragma mark
#pragma mark Memory management methods
//---------------------------------------------------------------
- (void) dealloc {
[self.tableView setDelegate:nil];
[self.tableView setDataSource:nil];
}
Swift code:
deinit {
self.tableView.dataSource = nil
self.tableView.delegate = nil
}
[ 2 ] Global object of subclass also needs to override -dealloc method and release relevant objects and delegates. Similar their dealloc method must have to be called.
e.g.
Check this scenario: Let's say you have created a subclass of UIView (or any other view) with the name MenuView. And you have created a global variable of this class in your view controller, than it's dealloc method will be called before view controller's dealloc method.
#property (nonatomic, strong) MenuView *menuView;
So here MenuView class needs to be override the -dealloc method and must be called before your view controller's dealloc method is called.
So that before leaving whole container its child views are release and removed from memory.
Check this in Instruments for MenuView class also that object is still their or not?
If it's dealloc method is not called than object of MenuView class is still in memory and your view controller class is referencing that object. So even if your view controller's -dealloc is called it will not deallocated because MenuView class object is live.
[ 3 ] You need to set weak reference to the IBOutlet. e.g.
Objective C code:
#property (nonatomic, weak) IBOutlet UITableView *tableView;
Swift Code:
#IBOutlet weak var tableView: UITableView!
Feel free to ask if anything is not clear.
Dont use the image asset... Copy the images to you a folder into your project an load it by code with imageWithContentOfFile it works to me
Try to use imageWithContentsOfFile to load image.
As rmaddy said,imageNamed will let your image stay in cache,this is the reason for your memory problem
One of the things I don't like about WYSIWYG/visual programming, is when you get to a point where you need to step outside of the box, you're left scratching your head. I'm at such a point right now along my iOS learning curve.
I have a custom UICollectionView thing. In two places (create and edit points), I need to present a list to the user to enable/disable (select/deselect) items from a list. So I go to the storyboard and whip up something like this:
In the past, following tutorials, I would control-drag a link from some control to the NavigationController show in the middle, I would tell it was a modal segue, tune a couple of methods, and get an arrow connecting the two for my efforts.
But in this case, I don't have obvious points to start that action from. I do have a + button. But it needs to do some other things first, and if all is well, then programmatically initiate the open, and somehow get notified of the state when it returns. Same for the individual cells, they might want to configure my table controller, and then open it, and be notified when it closes.
So I'm looking for a recipe of how one does this, what the key methods I should be looking for are. Apple's docs are great for reference, but I find figuring out how to do something from scratch difficult.
After creating a segue in your storyboard, you can initiate a segue any time programmatically by calling
[self performSegueWithIdentifier:#"segueID" sender:person];
Where "segueID" is a string you set for your segue in interface builder in the Identifier field in the identity inspector (right menu panel, 4th tab).
The segue does not need to be created from a control, you can just create one directly from one view controller to another. I usually do this on the right side menu by right-clicking on one view controller object and dragging to another one. This way, it acts as a segue that you can initiate programmatically any time you want.
As for getting notified when you come back to a view controller, (unless I'm misunderstanding your question) you can use either:
(void)viewWillAppear:(BOOL)animated
(void)viewDidAppear:(BOOL)animated
Create a UINavigationController programmatically with a desired view controller set as a root view controller. Here is an example of what you could put in a method invoked when user taps the plus button:
UIViewController *vc = [[UIStoryboard storyboardWithName:#"YourStoryboardName" bundle:nil] instantiateViewControllerWithIdentifier:#"YourViewControllerID"];
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
[self presentViewController:nc
animated:YES completion:nil];
To get a state, or information about the selected items you can use Delegation and declare a protocol. Example:
#protocol YourSampleDelegate <NSObject>
- (void)didSelectItem:(NSObject *)item;
#end
Then, your view controller (the one with the plus sign) should implement this protocol:
#interface ViewController : UIViewController<YourSampleDelegate>
...
#end
#implementation ViewController
...
#pragma mark - YourSampleDelegate conformance
- (void)didSelectItem:(NSObject *)item;
{
// Do something with the item.
}
#end
You also have to create a delegate property in a view controller with collection view and set the view controller with a plus as a delegate. There are tons of examples on the Internet. I hope this shows you the right direction.
In order to get my custom menu up and running, I've ended up using a UITabBarController and need to change the view displayed programmatically, vs the standard tabbed menu on screen.
Everything is working as expected except on thing. I am attempting to use:
[self setSelectedIndex:index];
This code is inside my UITabBarController subclass in a custom delegate method. (This is so I can programmatically adjust the view when interacting with my menu). However, while this code is called, it doesn't do anything?
Does it HAVE to be called from one of the tabbed views? I was hoping to run it from inside the TabBarController to avoid repeating the code in each tabbed sub controller.
UPDATE:
Just found that using [self setSelectedIndex:index]; works fine in viewDidLoad. But when it is called inside the delegate method, it doesn't change view. It is using the right index number and getting called, but not doing anything from that method.
Also, it seems the tab controller is a different object when I log self in viewDidLoad vs my delegate method. So why would I be loosing the reference to the original controller?
It's just a UITabBarController in a container in another view controller.
Delegate Code:
#Interface
#protocol SLMenuDelegate <NSObject>
#required -(void)menuDidChangeViewToIndex:(NSInteger)index;
#end
#property (nonatomic, assign) id<SLMenuDelegate>menuDelegate;
#Implementation
#synthesize menuDelegate;
self.menuDelegate = [self.storyboard instantiateViewControllerWithIdentifier:#"TabBarViewController"];
[menuDelegate menuDidChangeViewToIndex:[self.menuItemButtons indexOfObject:sender]];
UITabBarController
-(void)menuDidChangeViewToIndex:(NSInteger)index
{
[self setSelectedIndex:index];
}
Setting breakpoints and running NSLogs and there is no question that the method gets called and all code runs.
Try using delayed performance:
dispatch_async(dispatch_get_main_queue(), ^{
[self setSelectedIndex:index];
});
I didn't manage to find a solution to the exact issue, but I found an equally good way of resolving my issue simply.
I stopped using a delegate to send my button tap message and change the view. Instead I did the following:
SLTabBarViewController *tabBar = [self.childViewControllers objectAtIndex:0];
[tabBar setSelectedIndex:[self.menuItemButtons indexOfObject:sender]];
This gets the embedded tab bar controller and I simply directly change the view from the original view controller from which the button tap comes from.
It may not be an intelligent solution, but its a simple and functional one which doesn't create any problems.
I am trying to create an application with a feature similar to facebook's chat bubbles.
When the user navigates to a certain page (InCallViewController), they can connect to another person via video chat. When they navigate out of this page after connecting, I would like the video view to stay floating on the screen, but allow them to do what ever they want to do in the app.
In order to do this, I have made an InCallViewController class, which will allow the user to connect with the other person. Once connected, the video is displayed in a view. This view is movable (similar to facebook's chat bubbles) and displays the video chat perfectly, however when I exit the page and go to another page (AccountViewController) in the app, I am unable to keep this view on the screen. I have tried many things, including setting this view as a subview in the later pages. However when I do this, the subview is not displayed.
MyAccountView.m
- (void)viewDidLoad
{
[super viewDidLoad];
InCallViewController *inCallViewController = [[InCallViewController alloc] initWithNibName:#"InCallViewController" bundle:nil];
[self.view addSubview:inCallViewController.previewView];
[self.view bringSubviewToFront:inCallViewController.previewView];
(Do some other set up stuff)
}
InCallViewController.h
#interface InCallViewController : UIViewController <UIAlertViewDelegate>
{
CGPoint currentTouch;
NSArray *viewArray;
AVAudioPlayer *audioPlayer;
}
#property (weak, nonatomic) IBOutlet UIView *previewVideoView;
The previewView is a UIView in the InCallViewController class. This is hooked up in the IB, and works perfectly when in the InCallController class. The problem is, it won't show up when adding it as a subview in another class. I am wondering what I am doing wrong, or if there is a better way to keep the "previewView" remaining on the screen after I exit InCallViewController.
Thanks
You should consider implementing a container viewController. Since iOS6 and xcode 4.5 this has been made pretty straightforward.
The containing viewController can be handling your previewViews which are overlayed over whatever viewController is currently contained in it.
You can compare what you want to achieve with what Apple has achieved with a UInavigationController (also a container view controller): it contains viewController that are happily showing their content, but the navigationController makes sure the navigationBar is always present, for all viewControllers, even during animations.
Apple has some good documentation and even a WWDC session on this.
Hacking your way into [[UIApplication sharedApplication] keyWindow] is extremely poor design, and a blatant violation of the MVC pattern. It works, but it is a hack nonetheless and might give you headaches in the future.
You can add previewView to [[UIApplication sharedApplication] keyWindow] as a subview so that it appears on all your views and above each of them.
This may be an easy answer for someone.
I first built an a navigation app that had a table loaded from SQLite. The rootViewController (UITableViewController) is loaded from the mainWindow.xib . I added the search and scope functions, and push a detailed view (UIViewController) from a row selection just fine, and can navigate back and forth to and from the table and filtered results, search, with scoping different searches. All is good, no errors, crashes, or warnings.
Now I have tried to add a tabBar interface on top of the rootViewController...after 2 days I have got the TabBarController to display (I was trying to implement it directly on the RootviewController, but found I had to implement it in the mainWindow) and I can select different views (xib) from the tab bar. I have one tab that has no view assigned to it in IB, and I can see the RootViewController load as it did before as the first screen in this tab view, even though RootViewController is not assigned in the tab.
The problem is I cannot click on a row in the table when it loads this way. Alternatively if I create a tab, calling the RootViewController, I get the search bar on the top, but the table is empty. I feel this is somehow due to my appDelegate loading the rootViewController, and me not knowing how to get it to "load" or "reload" into the tab, or something like this. I tried creating a separate "search.xib" that was identical to the original mainWindow before adding the tab bar, then trying to load that in the TabItem, so it called the appDelegate first, but no cigar: crash. I verified the search.xib works fine, as I put it as the info.plist "Main nib file base name", and this loads fine and works as before this BS of adding a tabBarController...
I would start pasting code, but not sure what someone would need to know what is missing or wrong. I do call [self.tableView reloadData] in -(void)viewDidLoad, in RootViewController.m but it is not helping this problem at all.
Can anyone help?
Mac OS X is version 10.5.8, and I am using XCode 3.1.4.
// Create and configure the main view controller.
RootViewController *rootViewController = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];
rootViewController.violinMakers = violinMakers;
[violinMakers release];
// Add create and configure the navigation controller.
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.navController = navigationController;
[navigationController release];
// Configure and show the window, Override point for customization after app launch
[window addSubview:[navController view]];
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
Thanks for trying to help!
The last section above appears to be where I should get the RootViewController to be on top of the stack, but not sure how.
// <AppName>AppDelegate.h
#interface <AppName>AppDelegate: NSObject <UIApplicationDelegate,
UITabBarControllerDelegate, UINavigationControllerDelegate>{
UIWindow *window;
UINavigationController *navController;
UITabBarController *tabBarController;
}
#property (nonatomic, retain) IBOutlet UIWindow *window;
#property (nonatomic, retain) IBOutlet UINavigationController *navController;
#property (nonatomic, retain) IBOutlet UITabBarController *tabBarController;
#end
Ben - I do realize what you are saying about the rootViewController being behind the tabBarController. I just don't know how to set the tabBar as the main view, and the navController as a tab of it. I will look at the tutorial you mention and see if that helps explain it. Thanks Ben!
09-10-14 update
Some progress!
I now have the view in the tab with the with the NavBar at the top. I know it is coming from the correct .xib file as the title and the search can now be clicked on, and the scope buttons are the correct names. .... but as before, if I got to this stage or even close, the table information is not loaded into this tab(all cells are blank). How do I get my table to load properly in this cell? I know it is loading on launch, as if there is no view assigned at all to this window, I can see the table n Nav, but cannot click on it.(So close, yet so far away). I now have the tab set up correctly, but the table is not loading properly... rrrr
I have the typical
[self.tableView reloadData];
in the viewController.m in the method:
- (void)viewWillAppear:(BOOL)animated
and in
- (void)viewDidAppear:(BOOL)animated
and in
- (void)viewDidLoad
and tried it some other methods as well, but it is not reloading the table info when I select the tab.
Any ideas on how to reload this table in the TabBar view properly ?
I did find a good tutorial on tabBars that went a bit further than some others in explaining with IB. Twillo Embed Nav in a Tab Tutorial
It would appear that you're adding your tab bar controller on TOP of your table controller. It's not clear where you set up tabBarController, but you should only be adding ONE view to your window (in the third-to-last and second-to-last lines you are adding two).
The basic premise for Navigation and TabBar controllers is that your Tab Bar controller is the primary, and you'll add the Navigation controller as one of its tabs. Then, add the tabBarController.view to your window. Which ever tab is selected will be the visible one.
There's a tutorial posted on the web on this subject.
Basically here is what I have found for those of you with the same problem.
It is not easy, or really suggested by Apple it instantiate a tab bar later in a program. Yes it can be done, but now I would suggest a different method. I found a modal view controller works just a good as a tabBar, abeit smaller, but takes less real-estate, because it can be placed in the navigation bar. If you are planning an app with tabs, start with a tab based app and then customize it, don't try to change the navigation structure later, which is the underlying issue. Secondly I have found Apple's documentation on Interface Builder less than satisfactory. Everyone seams confused and it's implementation limits the actual final product cusomization, not making it easier. Not to mention more confusing on "wiring" all the elements together in it. That really sucks, and I took 2 weeks in trying various methods to make it work. It is not impossible, just incredibly unintuitive, and a mistake on my part to change paddles half way down stream. Look at the alternatives to the way you want to give access to the information, and I think you will find better ways like the modal view to accomplish this, or by simple buttons with IB actions to access different views or further information.
Happy Programming!
Kirk