I'm using this [tutorial] to save user's customized TabBarItems order as my app has more than 5 tabs
The tutorial's concept is to save the TabBarItem titles in the NSUserDefault and load them next time app is opened.
It's working great for english language as my TabBarItem titles are set initially in the XIB file
But the problem is that when my app's other language is loaded, as TabBarItem titles are changed to the selected language upon starting the app
Thus when the titles got saved after re-ordering the TabBarItems for the language that is different than the titles set in the XIB file, the TabBarItem are not loaded at all next time the app started! Which I think the tutorial I used is only work for TabBarItem titles when they are identical to TabBarItem titles defined in XIB file, not when those TabBarItem titles got changed programmatically based on the app language!
- (void)applicationWillTerminate:(UIApplication *)application {
NSMutableArray *savedOrder = [NSMutableArray arrayWithCapacity:tabController.viewControllers.count];
NSArray *tabOrderToSave = tabController.viewControllers;
for (UIViewController *aViewController in tabOrderToSave) {
[savedOrder addObject:aViewController.tabBarItem.title];
}
[[NSUserDefaults standardUserDefaults] setObject:savedOrder forKey:#"savedTabOrder"];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setTabOrderIfSaved];
}
- (void)setTabOrderIfSaved {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *savedOrder = [defaults arrayForKey:#"savedTabOrder"];
NSMutableArray *orderedTabs = [NSMutableArray arrayWithCapacity:tabController.viewControllers.count];
if ([savedOrder count] > 0 ) {
for (int i = 0; i < [savedOrder count]; i++) {
for (UIViewController *aController in tabController.viewControllers) {
if ([aController.tabBarItem.title isEqualToString:[savedOrder objectAtIndex:i]]) {
aController.tabBarItem.title = NSLocalizedString(aController.tabBarItem.title, nil);
[orderedTabs addObject:aController];
}
}
}
tabController.viewControllers = orderedTabs;
}
}
Try using the tabBarItem's tag property instead.
aController.tabBarItem.tag
Related
How the heck do you get the full image URL on iOS with UIImagePickerController using new PHAsset stuff, since the ALAssetLibrary stuff was deprecated?
I was having an awful time finding the answer to this, so to help people in the future, here is my code that works to get the actual file URL to a photo selected with the built-in iOS UIImagePickerController. Still a little nervous about using the ALAsset URL to get the PHAsset, if you know of another way to do that then by all means please share.
- (void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSURL* sourceURL = [info objectForKey:UIImagePickerControllerReferenceURL];
if (sourceURL != nil) {
PHAsset* asset = [[PHAsset fetchAssetsWithALAssetURLs:#[sourceURL] options:nil] lastObject];
PHContentEditingInputRequestOptions* assetOptions = [[PHContentEditingInputRequestOptions alloc] init];
[assetOptions setNetworkAccessAllowed:false];
[asset requestContentEditingInputWithOptions:assetOptions
completionHandler:^(PHContentEditingInput* contentEditingInput, NSDictionary* info) {
NSURL* fullImageURL = contentEditingInput.fullSizeImageURL;
printf("Hey, got the actual image URL: %s\n", [[fullImageURL absoluteString] UTF8String]);
}];
}
// ...finish and dismiss the UIImagePickerController
}
I'm trying to create a split view app with multiple detail view controllers. I'm having trouble with the basic setup/skeleton of the app. First I tried using xcode's Master-Detail Application template. The problem is that the classes from the template look something like this:
MasterViewController.h
MasterViewController.m
DetailViewController.h
DetailViewController.m
But what I want is something like this:
MasterViewController.h
MasterViewController.m
TitleViewController.h
TitleViewController.m
DateViewController.h
DateViewController.m
...
I can't figure out how to get my view controllers to load when user selects a new row.
I also tried using the sample MultipleDetailViews app from Apple, but the sample app has multiple issues for me including the fact that it uses nib files, which I don't want.
Can anyone help? Is there some tutorial about how to set up a split view app with multiple detail view controllers (without nibs)? Thank you!
*response:
Thanks! Could you post the link for the BigNerdRanch one? I couldn't find it. Also, I couldn't follow the Raywenderlich one because it uses an older version of xcode. Following your sample project, I think I'm close, but I'm getting a "terminating with uncaught exeption...". Here's what I did:
Create new project from Master-Detail template.
Add files MyTableViewController.h and MyTableViewController.m.
In MasterViewController.m, set the number of sections to 1 and the number of rows to 2.
In MasterViewController.h add property "MyTableViewController *myTableViewController".
(When user clicks 1st row, detailViewController should show, when he clicks 2nd row, myTableViewController should show.)
In MasterViewController.m, change didSelectRowAtIndexPath to:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int row = [indexPath row];
if( row == 0 ) {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.detailViewController];
NSArray *vcs = [NSArray arrayWithObjects:[self navigationController],nav, nil];
[[self splitViewController] setViewControllers:vcs];
}
else {
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:self.myTableViewController];
NSArray *vcs = [NSArray arrayWithObjects:[self navigationController],nav, nil];
[[self splitViewController] setViewControllers:vcs];
}
}
The project runs, but when I click on the 2nd row (row == 1) I get the exception.
*update 2
- (void) tableView: (UITableView *) tableView didSelectRowAtIndexPath: (NSIndexPath *) indexPath {
int row = [indexPath row];
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
UINavigationController *detailNav = [delegate.splitController.viewControllers objectAtIndex: 1];
NSArray *viewControllers = nil;
switch (row) {
case 0:
viewControllers = [[NSArray alloc] initWithObjects: delegate.dateController, nil];
break;
case 1:
viewControllers = [[NSArray alloc] initWithObjects: delegate.repeatController, nil];
default:
break;
}
[delegate.splitController removeFromParentViewController];
detailNav.viewControllers = viewControllers;
[delegate.window addSubview: delegate.splitController.view];
}
There's actually a great tutorial by the BigNerdRanch on how to use UISplitViewControllers. Honestly, there's no difference between using them to push a different UIViewController as the Detail or Master viewController. They just need a reference to one another and when an event happens, you push a new viewController to one side or the other. I've attached a sample project below for you to reference.
sample project: link
Here's another tutorial on setting one up via Raywenderlich: link
So the following happenins inside a UITableViewController which is the MasterViewController.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 1. get the controllers from the split view [master, detail]
let controllers = split.viewControllers
// 2. get nav controller for the detail + the current storyboard
let navigationController = controllers[controllers.count-1] as!
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// 3. create the appropriate view controller (vc) then just replace the navigation controllers root vc with your new one using 'navigationController.setViewControllers([vc] ...'
switch indexPath.row {
case 1:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailTableViewController") as! DetailTableViewController
navigationController.setViewControllers([vc], animated: false)
case 2:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailCollectionViewController") as! DetailCollectionViewController
navigationController.setViewControllers([vc], animated: false)
default:
let vc = storyboard.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
navigationController.setViewControllers([vc], animated: false)
break
}
}
You can do the same thing with segues. From the MasterViewController in the storyboard, drag a segue to a navigation controller and set the segue kind as a 'Show Detail (e.g. replace)'. Then from there in you didSelectRowAt you can call performSegue with identifier. Make sure that your segue has an 'Identifier'
Have a look at this video
Okay so I have my ImagePicker all setup and the ALAssetLibrary setup to get the Picture and Title (if it exists for existing pictures or generic text for a new picture) but now I'm trying to figure out if there's a way I can access this information outside of the block call from the assetForURL method. So here's my code just so I can show what's happening (this is in the viewDidLoad method of a screen that is displayed after a picture selection is made)
__block NSString *documentName;
__block UIImage *docImage;
NSURL *resourceURL = [imageInfo objectForKey:UIImagePickerControllerReferenceURL];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *asset)
{
ALAssetRepresentation *imageRep = [asset defaultRepresentation];
//Get Image
CGImageRef iref = [imageRep fullResolutionImage];
//If image is null then it's a new picture from Camera
if (iref == NULL) {
docImage = [imageInfo objectForKey:UIImagePickerControllerOriginalImage];
documentName = #"New Picture";
}
else {
docImage = [UIImage imageWithCGImage:iref];
documentName = [imageRep filename];
}
};
// get the asset library and fetch the asset based on the ref url (pass in block above)
ALAssetsLibrary *imageAsset = [[ALAssetsLibrary alloc] init];
[imageAsset assetForURL:resourceURL resultBlock:resultblock failureBlock:nil];
Now I want to be able to use the two variables (documentName and docImage) elsewhere in this ViewController (for example if someone wants to change the name of the document before they save it I want to be able to revert back to the default name) but I can't seem to figure out what I need to do so these variables can be used later. Don't know if this makes much sense or not, so if I need to clarify anything else let me know.
Okay so I figured out that the problem wasn't with the code but with my logic on how I was using it. I was trying to do this on the Modal View that was presented after an image was selected instead of just doing this in the ImagePicker screen and then calling the Modal window inside of the result block code.
This is driving me nuts!
I have a generic UITableViewController class with a generic prototype cell, with an identifier "myCell". Developing under ARC, iOS5 and using Storyboard, I am using the following method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
cell.textLabel.text = #"Title";
return cell;
}
Everything is hooked up in the storyboard, file owner, etc. I have several other UITableViewController in my project, using the same concept. All are working.
This particular class of UITableViewController doesn't want to work! keep throwing me the exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
Nslog'ed --> [tableView dequeueReusableCellWithIdentifier:#"myCell"] = (null)
Any idea and help is greatly appreciated!
EDIT:
For simplicity:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
Things that I have already done:
1. delete and create and set up the UITableViewController in storyboard!
2. Restarted Xcode
3. Restarted simulator
4. Restarted computer!!!!
5. Deleted and built the app over and over!
None worked.
By the way, I tried
if (cell == nil)
cell = [UITableViewCell alloc] initWithStyle....
.. and it will solve the problem, but again... this is ARC, storyboard, ... I am not supposed to do that. Xcode is supposed to take care of it!
You have to create your cell:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"myCell"] autorelease];
}
cell.textLabel.text = #"Title";
return cell;
}
Eventually I found the bug. I can't say I found the root cause, but investigating line by line, this is what I found and worked for me.
I have some objects in my UITableViewController that need to be alloc/init'ed before the view gets loaded, so that when callers can set them to pre-determind values. Since viewDidLoad is too late, I put them in initWithCoder method.
Commeting out and re-writing the initwithCoder method solved the problem. It seemed to me, that initWithCoder method was initing the UITableViewController as some different!
I had the same problem and just managed to overcome it.
If you are creating cells data dynamically you have to do the following things:
Select the table view.
Then go to the object inspector to the third tab from the right.
The first option is named: "Content". Change the selection from "Static Cells" to "Dynamic Prototypes".
Make sure you set the identifier of the cell properly and it is the same name you use on the "dequeueResableCellWithIdentifier"
It worked for me. I stopped getting "nil" cell values and everything seems to work properly.
if the "IF" statement is falling to true (cell == nil) for the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"myCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"myCell"] autorelease];
}
cell.textLabel.text = #"Title";
return cell;
}
then the name #"myCell" was either misspelled (Doesn't match) or is missing from the storyboard identifier field for the cell.
I search Google to find a good answer but the most tutorials are shown in previous Xcode Versions...
Also, I don't want to drag-n-drop cells from the Interface Builder, but to control the Table View programmatically (from an NSObject subclass file).
What I currently do is this: 1. Create a file named tableController.h that is a subclass of NSObject.
2. I create an NSObject Object in my Nib File (and set it as a subclass of tableController).
3. I drag a Table View to my window.
4. I CTRL+Drag from the Table View to my tableController.h so to create the outlet "tableView"
5. I create these functions in the interface file:
-(int)numberOfRowsInTableView:(NSTableView *)cocoaTV;
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row;
6. I implement the functions like this:
-(int)numberOfRowsInTableView:(NSTableView *)cocoaTV{
return 5;
}
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row{
NSArray *tvArray = [[NSArray alloc]initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
NSString *v = [tvArray objectAtIndex:row];
return v;
}
Then I CTRL+Drag from the Object in the Interface Builder to the Table View to set the dataSource and to set it as delegate.
When I build and Run the App it shows that it has created the 5 Rows but in every cell in every column it says "Table View Cell".
Any help would be appreciated....
-(id)tableView:(NSTableView *)cocoaTV:objectValueForTableCollumn:(NSTableColumn *)tableCollumn row:(int)row is wrong.. i'm not sure how it compiles, to be honest (unless there was an error copy/pasting it). the method should look like:
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSArray *tvArray = [[NSArray alloc]initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
NSString *v = [tvArray objectAtIndex:row];
return v;
}