Obtaining ViewController Identifier for "Back" UIButton no UINavigationController - ios

I am very new to Xcode and Objective-C as a whole. I have begun my first coding project/app this past week and I am running into a quick problem.
When creating my app, I started by dragging ViewControllers from the right Utilities menu section into my workspace area. I then added UIButtons from the same menu and Control+dragged a modal connection between the two (when the button is pressed, the view changes from one controller to the other?). My experience has been largely UI based and very little coding.
My Issue:
I have 5 ViewControllers each individually dragged from the Utilities menu. Four of them are linked by UIButtons titled "Next" and "Prev" according to the order I want them navigated. If at any time, a button present on all for ViewControllers is pressed on any given ViewController, you are presented with a separate "Menu" ViewController that has a "Back" UIButton. When this button is pressed, I want the ViewController to be switched back to whichever of the 4 VC's I was previously on when I pressed "Menu." All of the ViewControllers are in class ViewController.
Logically, this can be accomplished through setting a couple integers:
#implementation ViewController
int buttonPressedHere = 0;
- (IBAction)view1menu:(id)sender{
buttonPressedHere = 1;
}
- (IBAction)view2menu:(id)sender{
buttonPressedHere = 2;
}
- (IBAction)view3menu:(id)sender{
buttonPressedHere = 3;
}
- (IBAction)view4menu:(id)sender{
buttonPressedHere = 4;
}
- (void)setPrevious{
if(buttonPressedHere == 1)
//insert code to return to previous ViewController 1
if(buttonPressedHere == 2)
//insert code to return to previous ViewController 2
if(buttonPressedHere == 3)
//insert code to return to previous ViewController 3
if(buttonPressedHere == 4)
//insert code to return to previous ViewController 4
}
#end
The issue here is, I don't know how to get the identifier of the ViewController I want to switch to. Is there an easier way? Help!

First, define class for previous ViewControllers in Xib or Storyboard:
This is usually needed, you will custom the previous four views' activities further. The custom view controller class should inherit from UIViewController.
Then, you are able to control the pop action by following code:
- (void)setPrevious {
if (buttonPressedHere == 1) {
[self popToPreviousViewControllerWithClass:[FirstViewController class]];
}
else if (buttonPressedHere == 2) {
[self popToPreviousViewControllerWithClass:[SecondViewController class]];
}
else if (buttonPressedHere == 3) {
[self popToPreviousViewControllerWithClass:[ThirdViewController class]];
}
else if (buttonPressedHere == 4) {
[self popToPreviousViewControllerWithClass:[FourthViewController class]];
}
}
- (void)popToPreviousViewControllerWithClass:(Class)class {
for (UIViewController *previousViewController in self.navigationController.viewControllers) {
if ([previousViewController isKindOfClass:class]) {
[self.navigationController popToViewController:previousViewController animated:YES];
break;
}
}
}

Related

SWReveal - change frontviewcontroller on application start

I am using SWRevealViewController to implement a side menue in my application. in storyboard i have set the sw_front segue. this segue is loading the MainView on every start of application. now i want to give the user the possibility to change the first displayed viewcontroller.
How can i do that? I know there is a property setFrontViewController: but where should i use this to load the front during application start?
I cant use it in the viewDidLoad of the frontViewController which i set in Storyboard. That could not be a solution, so i would need to do it in every view controller to grab and check the "should load another view controller as first or frontViewController"
I have understood what you have problem and what you want to do i have also the same kind of thing to do, so what i have done create the custom segue in the swrevealviewcontroller that i have also mention in my code.
and modified the two methods perform and loadstoryboardcontroller methods, you can also see in my code.
It will definetely work for you as well.
Have a happy coding cheers.
My code is below:
I have done this the code you need to change is below.
- (void)loadStoryboardControllers
{
if ( self.storyboard && _rearViewController == nil )
{
//Try each segue separately so it doesn't break prematurely if either Rear or Right views are not used.
#try
{
[self performSegueWithIdentifier:SWSegueRearIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueFrontIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueRightIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueCustomIdentifier sender:nil];
}
#catch(NSException *exception) {}
}
}
#pragma mark - SWRevealViewControllerSegueSetController segue identifiers
NSString * const SWSegueRearIdentifier = #"sw_rear";
NSString * const SWSegueFrontIdentifier = #"sw_front";
NSString * const SWSegueRightIdentifier = #"sw_right";
NSString * const SWSegueCustomIdentifier = #"sw_custom";
#pragma mark - SWRevealViewControllerSegueSetController class
#implementation SWRevealViewControllerSegueSetController
- (void)perform
{
SWRevealControllerOperation operation = SWRevealControllerOperationNone;
NSString *identifier = self.identifier;
SWRevealViewController *rvc = self.sourceViewController;
UIViewController *dvc = self.destinationViewController;
if ( [identifier isEqualToString:SWSegueFrontIdentifier] )
operation = SWRevealControllerOperationReplaceFrontController;
else if ( [identifier isEqualToString:SWSegueRearIdentifier] )
operation = SWRevealControllerOperationReplaceRearController;
else if ( [identifier isEqualToString:SWSegueRightIdentifier] )
operation = SWRevealControllerOperationReplaceRightController;
else if ( [identifier isEqualToString:SWSegueCustomIdentifier])//your conditional segue
operation = SWRevealControllerOperationReplaceFrontController;
if ( operation != SWRevealControllerOperationNone )
[rvc _performTransitionOperation:operation withViewController:dvc animated:NO];
}
#jogshardik has the correct answer for my question but i found also another solution.
In your storyboard you are connecting a custom segue "sw_front" with your viewcontroller. This viewcontroller is on every initial start your first front viewcontroller. So you just have to check in this controller if you need to set another front viewcontroller.
i set up a notification in this "sw_front" viewcontroller and also in your Side Menue View Controller add the notification handler.
if you want to change the view just fire the notification and let do the notifaction handler the rest (set your setFrontViewController: property).
So this also works and is update stable if you need to keep attention on newer versions.
if this information is not enough for you, give me a hint and i will post an example code or project when i have time.

Only sometimes get EXC_BAD_ACCESS when setting uitabviewcontroller viewcontrollers

I'm trying to create and set VCs for my tab view controller. There is a next button in each VC that either opens the next tab or loads a new set of tabs when it reaches the end. Inside both Type1.m and Type2.m:
- (IBAction)next:(id)sender {
if (self.tabBarController.selectedIndex < [self.tabBarController.viewControllers count] - 1) {
self.tabBarController.selectedIndex += 1;
}
else {
// load nextData
NSLog(#"nextData: %#", nextData);
if (nextData == nil) {
// essentially stop the next button from working
return;
}
self.tabBarController.selectedIndex = 0U;
[self.tabBarController setTitle:[nextData display_name]];
[self.tabBarController setViewControllers:[nextData loadTabs]];
}
}
loadTabs is a method of the Data class (which is a Core Data NSManagedObject subclass with a category).
Inside ParentData+Helpers.m It looks like:
- (NSArray *)loadTabs {
NSMutableArray *mut = [NSMutableArray new];
for (SubData *d in self.datapoints) {
if ([d.field_x isEqual:#YES]) {
[mut addType1:d];
else {
[mut addType2:d];
}
return mut;
}
addType1 and addType2 are nearly identical in their fields, but have different implementations. They look like:
- (TypeVC *)addType1:(SubData *)data {
TypeVC *vc = [[TypeVC alloc] init];
vc.datapoint = data;
return vc;
}
where TypeVC is either Type1 or Type2
So now I have a table view and each cell has an associated ParentData which has an array of SubData objects. When selected, it loads a UITabViewController with Type1 and Type2 VCs.
inside TableVC.m:
- (void)- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"toTabs" sender:self];
}
where toTabs leads to a UITabBarController in the storyboard.
In prepareForSegue:
UITabBarController presentedTabBarController = [segue destinationViewController];
[presentedTabBarController setTitle:self.selectedTitleName];
[presentedTabBarController setViewControllers:[self.selectedParentData loadTabs]];
So all that works fine.
So I run my app, get to the UITabBarController and the thing that should be there is. The first set of tabs has 2 tabs, so I hit next and it goes to the second one. I hit next, and it loads the next set of tabs and opens to the correct tab. I hit next, alls working good. I hit next four or five times, each time it works correctly. It doesnt matter if its Type1 or Type2, it works.
But then suddenly, I get to a specific one that has 2 tabs. It loads the first one (the one at index 0) and then I hit next. And suddenly I have EXC_BAD_ACCESS(code=1, address=0xWHATEVER) at the line self.tabBarController.selectedIndex += 1;. I don't understand why this would happen.

Using the same view controller multiple times

I'm new to Objective-C and have a question. Did the search multiple times but I couldn't find what I was looking for.
I'm using storyboard for this app. On the homescreen you've got some buttons with labels above them. Those labels should tell a number. When pushing the button you go to a new viewController where you have input that (after 'save') goes back to the homescreen and updates the label with the correct number. All that works great for one button and I'm very happy about it.
The problems are:
1. Since I have multiple buttons with labels, I want to use the same viewController to give input over and over again. I tried connecting every button to slide to the viewController under the identifier "AddData", but Xcode doesn't allow the same identifiers twice or more in storyboard. So I would need something else for this. Any idea?
2. Currently I use the following code to bring back the data to the homescreen:
homeScreenViewController
- (IBAction)unwindToHomeScreen:(UIStoryboardSegue *)segue;
{
inputDataViewController *source = [segue sourceViewController];
self.logoOneLabel.text = source.endTotalNumber;
}
inputDataViewController:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if (sender != self.saveButton) {
return;
} else {
if (endTotalLabelNumber > 0) {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
} else if (endTotalLabelNumber == 0 && totalLabelNumber == 0){
self.endTotalNumber = 0;
} else {
self.endTotalNumber = [NSString stringWithFormat:#"%.0f", totalLabelNumber + endTotalLabelNumber];
}
}
}
This works great for the one button, but how to use this with multiple? I heard about Delegates to use the same viewController multiple time and get data back to different places, but I just don't get it. Any help?
You shouldn't need delegates.
What you will need is a property on the view controller that handles input to it knows which button it is handling input for.
When you segue to the input controller, set this property, based on which button was pushed. When you unwind back, fetch this property to know which label to modify.
For example, in your input view controller's .h file, add a property like this:
#property (nonatomic,assign) NSInteger handlingTag;
Or something, whatever name makes sense to you.
Now you need to implement your home screen view controller's prepareForSegue:sender:.
Use the sender argument to determine which button was pushed, and based on that, set the input view controller's new handlingTag property based on the button in a way that you will know what to do with it when we unwind.
Now in the unwind method:
switch (source.handlingTag)
Create a switch structure based on the source's handlingTag property, and set the appropriate label based on this value.
As Jeff points out in the comments, it'd be a really good idea to define an NS_ENUM to use here for the property rather than an NSInteger. The NS_ENUM would allow you to name the values you're using.
There is a few different way to implement what you need. But i think most common its a delegate.
This is how your inputDataViewController looks like:
#import <UIKit/UIKit.h>
#protocol inputDataDelegate;
#interface inputDataViewController : UIViewController
#property (weak) id<inputDataDelegate> delegate;
#property (strong, nonatomic) NSNumber *buttonTag;
#end
#protocol inputDataDelegate <NSObject>
-(void) inputDataViewControllerDismissed:(id)data;
#end
Then in #implementation, you should in "save" button action, message to you delegate method :
[self inputDataViewControllerDismissed:#{#"buttonTag":buttonTag,#"endTotalNumber":endTotalNumber}
Next in homeScreenViewController connect delegate :
#interface homeScreenViewController : UIViewController<inputDataDelegate>
After that in #implementation:
-(void)inputDataViewControllerDismissed:(id)data
{
// if you use modal
[self dismissViewControllerAnimated:YES completion:nil];
// or if you use push
//[self.navigationController popViewControllerAnimated:YES];
switch (data[#"buttonTag"]) {
case 1:
self.lableWtiTagOne = data[#"endTotalNumber"];
break;
case 2:
self.lableWtiTagTwo = data[#"endTotalNumber"];
break;
// number of cases depend how many buttons you have
}
Also, most important, thing didn't forget send self to our delegate:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"inputDataController"])
{
inputDataViewController *inputCtrl = [segue destinationViewController];
inputCtrl.delegate = self;
inputCtrl.buttonTag = sender.tag
}
}

Drag and drop from Collectionview into ContactViewController doesn't work on iPad

I'm developing an app which uses splitView.
I'm using two items in splitView (Called them Customer and Supplier).
When I click on one of them I just use one viewController to display (called it: ContactViewController) and I use collectionView to display its data. To get data I just code it:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!dbManager.synchronized) {
if (contactType == ContactTypeCustomer)
[dbManager requestData:kDbCustomers predicate:nil target:self];
else if (contactType == ContactTypeSuppplier)
[dbManager requestData:kDbSuppliers predicate:nil target:self];
}
}
And when get successful:
#pragma mark
#pragma DBDelegate
- (void)requestDataCompleted:(NSMutableArray *)results
{
datasource = results;
[self.collectionView reloadData];
}
I use I3DragBetweenHelper downloaded from github.com
Embedded into my app to initial drag and drop. To do this, I call the below method into viewDidLoad of ContactViewController
- (void) initDragAndDrop
{
self.helper = [[I3DragBetweenHelper alloc] initWithSuperview:self.view
srcView:_collectionView
dstView:_collectionView];
self.helper.delegate = self;
self.helper.isDstRearrangeable = NO;
self.helper.isSrcRearrangeable = NO;
self.helper.doesSrcRecieveDst = NO;
self.helper.doesDstRecieveSrc = YES;
self.helper.hideDstDraggingCell = YES;
self.helper.hideSrcDraggingCell = NO;
}
The helper here is:
#property (strong, nonatomic) I3DragBetweenHelper *helper;
The problem is when I click on Supplier I can drag and drop the cell of collectionView into ContactViewController. Below method had called and worked:
- (BOOL)droppedOutsideAtPoint:(CGPoint)pointIn fromDstIndexPath:(NSIndexPath *)from
But when I click on Customer the above method doesn't call. I can't even drag my cell of collectionView into ContactViewController. Any help will appreciate.
Why is isDstRearrangeable set to NO ??? coz eventually you would grab something and drop it onto something ( dest ) and that something ( dest ) will be rearranged, am i wrong ??
does your drag and drop happen in the same place ?? ( i.e: UICollectionView )
if it does, then why do you have src as not rearrangeable ??? both your destination and src should be rearrangeable.
Try this and let me know how it goes :)
I dont know about this library but I think the problem is your srcView and dstView are the same. These should be different and must be either UITableView or UICollectionView.
If you are using UISplitViewController, its probably going to be the Master and Detail View Controllers used by the UISplitView

Modified button images lost during seque

I have three scenes (viewControllers) linked through modal segues (I don't want a navigation controller). The view has a series of buttons, each initially with a red-light image. When clicked they go to the next scene, whose action may affect the image on the preceding scene by making it a yellow or green light image.
If the button image is changed programmatically via setImage, and later if the button is clicked again, the button image always reverts to the initial red-light image. Similarly if I change the button images on scene 2, go back to scene 1, and then back to scene 2 again, the buttons are back to default, rather than retaining the previous button image state.
So, the two basic questions are:
How to prevent the button image from changing back to default when clicked.
How to have an visited scene remember it's previous settings rather than go back to initial settings when visited a second time.
Thanks,
Ray
If you're using modal transitions, you need to use dismissViewControllerAnimated:completion: to get back. I'm guessing that instead you're pushing from VC1 to VC2, and then pushing a new copy of VC1 in order to return, which is wrong.
1 question: Please, show your code for changing images, mb problem in it.
2 question: My answer for second question is make a manager for your button states, for example like below:
.h
NS_ENUM(NSInteger, LightState) {
LightStateRed,
LightStateYellow,
LightStateGreen
};
#interface LightManager : NSObject
+ (instancetype)sharedManager;
- (NSString *)imageForButtonNumber:(int)buttonNumber;
- (void)changeImageForButtonNumber:(int)buttonNumber;
#end
.m
#implementation LightManager {
NSMutableArray *buttonStates;
NSArray *images;
}
+ (instancetype)sharedManager {
static LightManager *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[LightManager alloc] init];
});
return sharedManager;
}
- (id)init {
self = [super init];
if (self) {
images = #[#"red_light", #"yellow_light", #"green_light"]; // Or whatever images have names in your app
buttonStates = [[NSMutableArray alloc] initWithCapacity:9000]; // Number of all buttons
for (int index = 0; index < 9000; index++) {
[buttonStates addObject:#(LightStateRed)]; // Initalize with default value
}
}
return self;
}
- (NSString *)imageForButtonNumber:(int)buttonNumber { // For restoring previous state
return images[[buttonStates[buttonNumber] integerValue]];
}
- (void)changeImageForButtonNumber:(int)buttonNumber { // On button click
if ([buttonStates[buttonNumber] integerValue] == LightStateGreen) { // If you want looping
buttonStates[buttonNumber] = #(LightStateRed);
}
else {
buttonStates[buttonNumber] = #([buttonStates[buttonNumber] integerValue] + 1);
}
}
#end
Or make manager from your first controller and pass states to modal controller through -initWithParams: and save back in first controller via delegate methods or whatever.

Resources