Only sometimes get EXC_BAD_ACCESS when setting uitabviewcontroller viewcontrollers - ios

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.

Related

Obtaining ViewController Identifier for "Back" UIButton no UINavigationController

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;
}
}
}

UISplitViewController crashes when using showDetailViewController

In my master view I have 4 static table rows. 2 of these rows drill down into a detail view within the master view and the other 2 replace the contents of the detail view. I control what happens with the didSelectRowAtIndexPath() method by calling showViewController() and showDetailViewController() functions as appropriate:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == 0) {
Master2TVC *m2tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-2"];
[self showViewController:m2tvc sender:self];
} else if (indexPath.row == 1) {
Master3TVC *m3tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"master-3"];
[self showViewController:m3tvc sender:self];
} else if (indexPath.row == 2) {
Detail2VC *d2vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-2"];
[self showDetailViewController:d2vc sender:self];
} else if (indexPath.row == 3) {
Detail3VC *d3vc = [self.storyboard instantiateViewControllerWithIdentifier:#"detail-3"];
[self showDetailViewController:d3vc sender:self];
}
}
The template for the Master-Detail template creates a reference from the master view to the detail view:
self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
If I understand things correctly, this reference exists so that the master side can send messages to the detail side. In my case the class of my detail view will change (Detail3VC, Detail2VC, etc) so I've decided to remove this line and do the messaging another way; however, now when I load any of my new detail views and change the rotation of the iPad the App sometimes crashes with the error EXC_BAD_ACCESS.
From what I understand EXC_BAD_ACCESS usually means there is an object hanging around somewhere that shouldn't be. I have been unable to find anything in the Apple documentation that talks about having to change anything else when using showDetailViewController() call. In fact, I thought that the reason for using showDetailViewController() is so that the splitViewController manages all the details and you don't have to in your own classes.
Anyone can see the error here?
I have confirmed the crash you encountered. It always, not sometimes, happens when changing iPad's rotation.
Anyway, it seems that we need to implement -targetDisplayModeForActionInSplitViewController: of UISplitViewControllerDelegate and return any value except UISplitViewControllerDisplayModeAutomatic.
- (UISplitViewControllerDisplayMode)targetDisplayModeForActionInSplitViewController:(UISplitViewController *)svc {
return UISplitViewControllerDisplayModePrimaryOverlay;
}

How can I alternate view controller and not lose data

I have two UIViewControllers, one is a UIPickerViewController, the Other a UITableViewController. Ideally the Picker should get a request from the user to add x amount of some item to the tableView. The Picker gets user inputs and assigns them to variables val1, val2, val3, where val1 is the number of items (number of rows) and val2 is the name or label for the item.
PickerViewController.m
- (IBAction)add:(id)sender
{
TableViewController *tvc = [[TableViewController alloc] init];
[tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:tvc animated:YES completion:nil];
}
TableViewController.m
-(void)setValues:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
self.val1 = newVal1;
self.val2 = newVal2;
self.val3 = newVal3;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"UITableViewCell"];
// This is just a header which holds my "Add" button
UIView *header = self.headerView;
[self.tableView setTableHeaderView:header];
[self addNew:self.val1 :self.val2 :self.val3];
}
- (void)addNew:(NSString *)newVal1 :(NSString *)newVal2 :(NSString *)newVal3
{
if(!self.numberOfRows){
NSLog(#"Initially no of rows = %d", self.numberOfRows);
self.numberOfRows = [self.val1 intValue];
NSLog(#"Then no of rows = %d", self.numberOfRows);
}
else
{
self.numberOfRows = self.numberOfRows + [newVal1 intValue];
NSLog(#"New no rows = %d", self.numberOfRows);
}
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.numberOfRows inSection:0];
// Only run when called again .. not initially
if(self.run != 0){
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[indexPath]withRowAnimation:UITableViewRowAnimationBottom];
self.run ++;
[self.tableView endUpdates];
}
}
// "ADD" button which should go back to the picker and get new items to add to the table
- (IBAction)testAdd:(id)sender
{
PickerViewController *pvc = [[PickerViewController alloc] init];
[self presentViewController:pvc animated:YES completion:nil];
}
Now, I realize every time I call the next view controller I am creating a new instance of it, but I don't know how else to do it, I figure this is the main problem. As of right now, I expect when I leave the tableview for the picker view and return the console should log "New no of rows = x" but that doesn't happen.
I know val3 isn't used and my addNew: may not be the best, but I just need it to handle the basic logging mentioned above and I should be able to take it from there.
Been stuck on this for days
Create a property for TableViewController, and only create it the first time you present it,
- (IBAction)add:(id)sender {
if (! self.tvc) {
self.tvc = [[TableViewController alloc] init];
}
[self.tvc setValues:self.val1 :self.val2 :self.val3];
[self presentViewController:self.tvc animated:YES completion:nil];
}
It's not entirely clear from you question, whether it's this presentation or the one you have in the table view class that you're talking about. It also looks like you're doing something wrong in terms of presentation -- you're presenting the picker view from the table view controller, and also presenting the table view controller from the picker. That's not correct, you should present which ever controller you want to appear second, and that controller should use dismissViewControllerAnimated to go back, not present another controller.
In testAdd you don't need to create a new instance and present it. If you want to go back to the presentingViewController, just use dismissViewControllerAnimated .
And you will go one controller up in the stack.

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.

Can't reload a collection view - null

I'm developing a simple app which downloads images from Dribbble, but I'm having problem reloading the data for my collection view. I have two views set up with ViewDeck, center is my main view which contains the collection view and another view contains table view with settings and from there I'm trying to call a method in the first view and reload data when item is tapped but it just doesn't work.
I tried to call the same method from the main window using button -> worked like a charm but from the second window it just doesn't update the data.
I tried to debug somehow and seems like my collection is null when the reload is called, no idea why.
SettingsViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *appDelegate = [[JKViewController alloc] init];
appDelegate.dataHasChanged = YES;
[appDelegate refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
MainView
- (void)refresh{
NSLog(#"refresh");
if(dataHasChanged)
{
switch (listType) {
case 0:
[self refreshWithList:SPListPopular];
break;
case 1:
[self refreshWithList:SPListEveryone];
break;
case 2:
[self refreshWithList:SPListDebuts];
break;
case 3:
[self refreshWithList:SPListPopular];
break;
default:
[self refreshWithList:nil];
break;
}
dataHasChanged = NO;
NSLog(#"Should refresh");
}
NSLog(#"%d", [self->shots count]);
NSLog(#"Collection view: %#",self.collectionView.description);
NSLog(#"self.list: %#",self.list);
NSLog(#"List type: %d", listType);
}
This doesn't work :/, but when I call it from button in the MainView it works.
- (IBAction)changeList:(id)sender {
[self refreshWithList:SPListDebuts];
}
Does anyone know what could be the issue?
Edit - Solved
Getting the right instance of the centerViewController
JKViewController *mainController = ((UINavigationController*)self.viewDeckController.centerController).visibleViewController.navigationController.viewControllers[0];
The reason that you are not seeing your data being updated is because you are creating a new view controller and telling that to refresh. This new view controller has been initialized but not added to your view hierarchy. What you want to do is message the existing view controller like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tap");
JKViewController *mainViewController = self.viewDeckController.centerViewController;
mainViewController.dataHasChanged = YES;
[mainViewController refresh];
[self.viewDeckController closeLeftViewAnimated:YES];
}
Also, please note that I have changed the variable name in my revision. Naming a UIViewController instance 'appDelegate' is very confusing.

Resources