Show Next and previous Detail UIView without going back to parent UITableView - objective-c

I have a Nav controller that starts at a table view. Each row pushes to a detail UIView. I would like to have a next and previous button on the Detail UIView that would show next and previous views without going back to parent UITableView. All details are saved in an array. how can i access that array and its current index in DetailViewController.m .Thanx in advance.

Another clean (and elegant) way is to build some sort of connection between the table view datasource, which is normally the UIViewController that contains the table, and the detail view: this can be done using the Delegate pattern, typical of Cocoa framework.
In such case you can define a DetailViewDataDelegate protocol with two methods only:
-(id)nextTableObjectFrom:(id)referenceObject;
-(id)prevTableObject;(id)referenceObject;
where referenceObject is the calling object, that is the object detailed in the DetailView.
So DetailView will be defined in this way:
#interface DetailViewController:UIViewController {
}
#property (nonatomic,assign) id currentDetailedObject;
#property (nonatomic,assign) id dataDelegate;
and of course when you call the controller, typical from the tableView:didSelectElementWithIndexPath:animated: method, you will do something like this:
DetailViewController *dv = [[[DetailViewController alloc] initWithNibName:nil bundle:nil] autorelease];
dv.dataDelegate=self;
dv.currentDetailedObject=[mySourceArray objectAtIndexPath:indexPath.row];
[self.navigationController pushViewController:dv animated:YES];
Finally in the DetailViewController when you need the next (or prev) element from the table you will simply call:
-(IBAction)nextButtonPressed {
self.currentDetailedObject = [self.dataDelegate nextTableObjectFrom:self.currentDetailedObject];
}
Of course the implementation details may change, and in particular the delegate methods, to be implemented in the table's UIViewController, will depend on the data structure.
The advantage of this approach, which can be at first sight complicated, is quite elegant and avoids to pass objects along controllers, which is often a source of memory issues. Besides with the delegate approach you can implement any complicated feature (e.g.: you can even manipulate table view objects directly from the DetailViewController, at your own risk of course!)

The cleanest way is to create a custom initializer for DetailViewController:
#interface DetailViewController : UIViewController
{
NSArray* allObjects;
NSUInteger displayedObjectIndex;
}
- (id) initWithObjectAtIndex: (NSUInteger) index inArray: (NSArray *) objects;
#end
#implementation DetailViewController
- (id) initWithObjectAtIndex: (NSUInteger) index inArray: (NSArray *) objects
{
self = [super initWithNibName:nil bundle:nil];
if ( self ) {
allObjects = [objects copy];
displayedObjectIndex = index;
}
return self;
}
#end
That way, a DetailViewController always knows both what object it is displaying details for and the previous/next objects, if any.

Related

NSTableView ViewBased never calling the needed delegate

I have a NSTableView where I wish to display a list of info.
Currently the viewForTableColumn method delegate never runs, but numberOfRowsInTableView does.
I have the NSTableViewDelegate and NSTableViewDataSource set in the ViewController head. And I set the tableview delegate and datasource to self. Does somebody know why it wouldn't run? I've added a screenshot and code below.
ViewController.h
#interface ViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
#property (strong) IBOutlet NSTableView *tableView;
#property (strong, nonatomic) NSMutableArray<App *> *installedApps;
#end
ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
_installedApps = [[NSMutableArray alloc] init];
_tableView.dataSource = self;
_tableView.delegate = self;
// Other stuff that populates the array
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return _installedApps.count;
}
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSTableCellView *result = [tableView makeViewWithIdentifier:#"appCell" owner:self];
result.textField.stringValue = #"Hello world";
return result;
}
The view is in a container view, I have the 'appCell' identifier set to the Table Cell View.
The array _installedApps is empty and numberOfRowsInTableView: returns 0. Thus, tableView:viewForTableColumn: is not called because there is no row to show. No rows also means no columns.
You should also ensure that you have configured your table view as view based in attributed inspector of the table view.
I can't see it in the screenshots, but...is the highlighted row of the view hierarchy (Table Cell View) the one with the appCell identifier?
[minutes pass...]
Oops; sorry. I see you've noted that above.
The reason I ask is that I made a new project from your code, changing the array type from App to NSString, added a one-column table view to the storyboard, linked it to the code, added a couple enties to the array in -viewDidLoad, and -- once I put the appCell identifier in the right place (duh) -- it all worked fine.
This is super strange, but I had the very same issue, everything connected correctly, number of rows being called, but not viewForTableColumn... In the end the following Answer proved to be the solution:
https://stackoverflow.com/a/13091436/3963806
Basically, I had setup the tableview straight out of the Object library, no layout constraints etc... Once I added layout constraints, the method started to be called... Super strange as I could see and click on the "rows" but they weren't populated correctly... I think it's down to clipping as mention in the linked answer.

Passing data back and forth using AppDelegate

To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies

Determine if instance of UIViewController subclass exists

How can I examine a list of all active objects that inherit UIViewController?
I'd like to know if an instance of MyViewController exists. Ideally I can get this information in a callback in UIApplicationDelegate (for example application:didReceiveRemoteNotification:).
I've tried logging something like navigationController.viewControllers w/ no luck. I've also tried topViewController and modalViewController properties on navigationController.
If you know for a fact that your rootViewController is a UINavigationController, you can iterate through the array of viewcontrollers and test it for a class type
BOOL success = NO;
NSArray *viewControllersArray = self.navigationController.viewControllers;
for (id vc in viewControllersArray)
{
if ([vc isKindOfClass:[MyViewController class]])
success = YES; // Found it!
}

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.

Handling multiple UISwitch controls in a table view without using tag property

I have a table view controller with multiple UISwitch controls in them. I set the delegate to the table view controller with the same action for all switches. I need to be able to determine what switch was changed, so I create an array of strings that contains the name of each switch. The indexes in the array will be put in the tag property of each UISwitch.
However, I'm ready using the tag property for something else, namely to find the right control in the cell in cellForRowAtIndexPath with viewWithTag! (There are several things I need to set within each cell.)
So, am I thinking along the right lines here? I feel I'm rather limited in how I find out exactly which UISwitch changed its value, so I can do something useful with it.
I fixed this by subclassing UISwitch like so:
#interface NamedUISwitch : UISwitch {
NSString *name;
}
It seems elegant (no index arrays required) and the tag property is free to do whatever it wants.
I read that you have to be careful with subclassing in Objective-C, though...
I have written a UISwitch subclass with a block based hander for value change control events which can help when trying to track which switch's value has changed. Ideally, we could do something similar with composition rather than subclassing, but this works well for my needs.
https://gist.github.com/3958325
You can use it like this:
ZUISwitch *mySwitch = [ZUISwitch alloc] init];
[mySwitch onValueChange:^(UISwitch *uiSwitch) {
if (uiSwitch.on) {
// do something
} else {
// do something else
}
}];
You can also use it from a XIB file, by dragging a switch onto your view, and then changing its class to ZUISwitch
You are close in your approach. What I have done in similar situations is create separate UITableViewCell subclasses, set the tag of the UISwitch to be the index.row of the index path, and only use that UITableViewCell subclass in a specific section of the table view. This allows you to use the tag of the cell to uniquely determine what cell has the event without maintaining a separate index list (as it sounds like you are doing).
Because the cell type is unique, you can than easily access the other elements of the cell by creating methods/properties on the UITableViewCell Subclass.
For example:
#interface TableViewToggleCell : UITableViewCell {
IBOutlet UILabel *toggleNameLabel;
IBOutlet UILabel *detailedTextLabel;
IBOutlet UISwitch *toggle;
NSNumber *value;
id owner;
}
#property (nonatomic, retain) UILabel *toggleNameLabel;
#property (nonatomic, retain) UILabel *detailedTextLabel;
#property (nonatomic, retain) UISwitch *toggle;
#property (nonatomic, retain) id owner;
-(void) setLable:(NSString*)aString;
-(void) setValue:(NSNumber*)aNum;
-(NSNumber*)value;
-(void) setTagOnToggle:(NSInteger)aTag;
-(IBAction)toggleValue:(id)sender;
#end
In:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ... prior iniitalization code for creating cell is assumed
toggleCell.owner = self;
[toggleCell setLable:#"some string value"];
[toggleCell setTagOnToggle:indexPath.row];
toggleCell.owner = self;
return toggleCell;
//... handle cell set up for other cell types as needed
}
Owner is the delegate for the cell and can then be used to initiate actions in your controller. Make sure you connect your UISwitch to the toggleValue Action, so that you can initiate actions in the delegate when the UISwitch changes state:
-(IBAction)toggleValue:(id)sender;
{
BOOL oldValue = [value boolValue];
[value release];
value = [[NSNumber numberWithBool:!oldValue] retain];
[owner performSelector:#selector(someAction:) withObject:toggle];
}
By passing the UISwitch with the method call, you can then access the index path for the cell. You could also bypass the use of the tag property by explicitly having an ivar to store the NSIndexPath of the cell and then passing the whole cell with the method call.
I realize I'm about three years late to the party but I've developed a solution without subclassing that I think is preferable (and simpler). I'm working with the exact same scenario as Thaurin's described scenario.
- (void)toggleSwitch:(id) sender
{
// declare the switch by its type based on the sender element
UISwitch *switchIsPressed = (UISwitch *)sender;
// get the indexPath of the cell containing the switch
NSIndexPath *indexPath = [self indexPathForCellContainingView:switchIsPressed];
// look up the value of the item that is referenced by the switch - this
// is from my datasource for the table view
NSString *elementId = [dataSourceArray objectAtIndex:indexPath.row];
}
Then you want to declare the method shown above, indexPathForCellContainingView. This is a seemingly needless method because it would appear at first glance that all you have to do is identify the switch's superview but there is a difference between the superviews of ios7 and earlier versions, so this handles all:
- (NSIndexPath *)indexPathForCellContainingView:(UIView *)view {
while (view != nil) {
if ([view isKindOfClass:[UITableViewCell class]]) {
return [self.myTableView indexPathForCell:(UITableViewCell *)view];
} else {
view = [view superview];
}
}
return nil;
}