I am trying to add anew row to a UITableView. The user enters data in a UITextField on a UIViewController and the control goes back to the original UITableView. What method writes to the table when new row is added. i seem to have tried everything. nothing seems to work.
The code from the comment below is:
tableItems = [NSMutableArray arrayWithObjects:#"Persian", #"Rag Doll", #"Siamese", #"Scottish Fold", #"British short hair", nil];
[self.tableView reloadData];
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"AddSegue"])
{
UINavigationController *nav = segue.destinationViewController;
AddItemViewController *aivc = [nav.viewControllers objectAtIndex:0];
aivc.TVC = self;
}
}
If you are storing the new row data in an array (for example) which is accessed by the UITableView then just reload the table as following:
[self.tableview reloadData];
I've updated your question with the code that you provided in your comment to another answer.
Bottom line, you don't tell us where your exception is being generated, nor what the exception is. You need to narrow that down if you expect us to help. Also, I'm not sure what to conclude from the fact that you suggest that the exception is being caused after the UITextField is entered and you're returning to the previous controller, but you didn't show us that code, but rather you showed us the code for segueing to the AddItemViewController. Are you getting the error when you first segue, or when you try to pop/dismiss to get back to the view controller with the UITableView?
With those fairly significant questions notwithstanding, I'd suggest two things:
First, make sure you're adding robust checking of your results, to prevent exceptions from happening. For example, a common problem is that one retrieves a value from a property or a NSArray, but for one reason or another, it's not the type of object that the code assumes it was (and requires it to be). In this case, you're assuming that the destinationViewController is a UINavigationController and furthermore, you're assuming that the first element in its viewControllers array is a AddItemViewController. You know your code, and perhaps you are confident of this fact, but the fact that you're getting an exception means that you probably need more robust error checking.
Therefore, I would suggest that you including NSAssert statements that will verify this fact. Using your prepareForSegue as an example, I might suggest altering it thusly:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"AddSegue"])
{
UINavigationController *nav = segue.destinationViewController;
NSAssert([nav isKindOfClass:[UINavigationController class]], #"destinationViewController is not a UINavigationController");
AddItemViewController *aivc = nav.viewControllers[0];
NSAssert([aivc isKindOfClass:[AddItemViewController class]], #"destinationViewController.viewControllers[0] is not a AddItemViewController");
aivc.TVC = self;
}
}
The NSAssert statements are good ways of testing values during testing to make sure that the objects you return are truly of the Class you thought they were. These NSAssert clauses are only used for errors that you're testing for during development, but should never occur in the production application. If your code makes implicit assumptions about what's being returned from a method, you can use NSAssert to validate those assumptions while you're debugging your app.
Now, I have no reason to know whether the values of nav or aivc are your problem, but this is an example of the sort of checking your code should be doing. It's simply a good practice (especially if you're struggling to find an exception), to make sure that the object you've retrieved is the correct type, upon which the rest of your code depends.
I have an ulterior motive here. Your code implies that you're navigating to a view controller that is, itself, embedded in its own navigation controller. You never said as much in your narrative, so these NSAssert statements simply verify this fact.
Second, if all of your robust verification of objects doesn't catch the problem, you must therefore identify the source of the exception yourself. Frequently you can decipher this by looking at the stack trace in the "Debug Navigator" or by reading the error in the console in Xcode. But, another (underappreciated, IMHO) tool, is exception breakpoints which can find the exact line of code that is causing the exception. If I'm encountering any exceptions, I'll routinely add an exception breakpoint on "All" exceptions. That way, if I'm running the program through my debugger, if it encounters an exception, it will stop the code at the offending line, greatly simplifying the process of identifying the source of the problem. It doesn't always work perfectly, but it frequently finds the source of the exception more quickly than other techniques.
Related
Recently I wrote some code where I tried to refer to an outlet on a UIViewController I'd just instantiated with [storyboard instantiateViewControllerWithIdentifier] and modify the subview that the outlet pointed to before presenting the ViewController. It didn't work because the ViewController's view hadn't loaded its subviews yet, including the one that my outlet referred to, so the property just gave me a null pointer.
After (with some struggle) tracking down the cause of my issue in the debugger, I Googled around and learned, through answers like this one, that I can cause the view to load its subviews without being displayed by calling the myViewController.view getter. After that, I can access my outlet without any problems.
It's a clear hack, though, and Xcode - quite rightly - doesn't like it, and angrily protests with this warning:
Property access result unused - getters should not be used for side effects
Is there a non-hacky alternative way to do this that doesn't involved abusing the .view getter? Alternatively, are there canonical/idiomatic patterns for this scenario involving something like dynamically adding a handler to be called as soon as the subviews are loaded?
Or is the standard solution just to replace myViewController.view with [myViewController view] to shut up Xcode's warning, and then live with the hack?
On iOS 9 or newer, one can use:
viewController.loadViewIfNeeded()
Docs: https://developer.apple.com/reference/uikit/uiviewcontroller/1621446-loadviewifneeded
I agree that forcing a view to load should be avoided but I ran into a case where it seemed the only reasonable solution to a problem (popping a UINavigationController containing a UISearchController that had yet to be invoked causes a nasty console says warning).
What I did was use new iOS9 API loadViewIfNeeded and for pre-iOS9 used viewController.view.alpha = 1.0. Of course a good comment above this code will prevent you (or someone else) removing this code later thinking it is unneeded.
The fact that Apple is now providing this API signals it can be needed from time to time.
Not sure how much cleaner this way, but it still works fine:
_ = vc.view
UPD: for your convenience, you can declare extension like below:
extension UIViewController {
func preloadView() {
let _ = view
}
}
You can read explaination by following URL: https://www.natashatherobot.com/ios-testing-view-controllers-swift/
merged Rudolph/Swany answers for pre ios9 deployment targets
if #available(iOS 9.0, *) {
loadViewIfNeeded()
}
else {
// _ = self.view works but some Swift compiler genius could optimize what seems like a noop out
// hence this perversion from this recipe http://stackoverflow.com/questions/17279604/clean-way-to-force-view-to-load-subviews-early
view.alpha = 1
}
If I understand you correctly, I think there's another fairly standard solution: move the outlet modification/configuration code into a viewDidLoad method (of the recently instantiated VC).
The topic is also discussed in this question.
It would require some restructuring, but it might give you a "cleaner" design in terms of MVC if your incoming VC handled its own configuration, and it would avoid the "You should never call this method directly" stricture on loadView.
You can call [myViewController loadView] to explicitly load the view, instead of abusing the .view getter. The .view getter actually calls loadView if necessary when called.
It's still not a very nice solution, since the UIView Documentation's section on loadView explicitly instructs that
You should never call this method directly
I'm trying to come up with a good way to organize transitioning between UIViewControllers that removes the logic from the controllers themselves. I came up with a pattern based on the Pro ObjC Design Patterns book that I liked, but I'd like to know how to improve it.
The pattern in a nutshell. Forgive the semi-pseudo code.
I subclassed the UINavigation Controller and created a method that all the View Controllers call when they need to transition.
-(void)requestViewChangeByObject:(id)object withData:(object*)someData{
if ([object isKindOfClass:[ViewController1 class]]) {
[self showViewController2Animated:YES withSomeData:nil];
}
if ([object isKindOfClass:[ViewController2 class]]) {
[self showViewController3Animated:YES withSomeData:someData];
}
if...
}
Then I just defined methods for each controller. Some just instantiate and push the controller and some set properties or pass information in.
-(void)showViewController2Animated:(BOOL)animated withSomeData:(object*)someDataVariable{
viewController2 *VC2 = [viewController2 defaultViewController];
[self pushViewController:VC2 animated:animated];
}
-(void)showViewController3Animated:(BOOL)animated withSomeData:(object*)someDataVariable{
viewController3 *VC3 = [ViewController3 defaultViewController];
VC3.someData = someDataVariable
[self pushViewController:VC3 animated:animated];
}
The reason I'm doing it this way is because it makes the application much more flexible in terms of changing around controller order and adding/removing controllers as the design and requirements change. We also tend to re-use apps and this makes it easier to reskin and reorganize to build a new application.
The main problem I have is the more complicated the application gets the bigger that method with all the if statements is going to get. It might get confusing if there's more logic involved than just push viewController2 if the request comes from viewController3. I'd really like to know how I can improve this so that it's more flexible and less likely to cause confusion and errors.
Second, it's not very flexible when you add passing data around to the problem. I ended up taking the parameter out that accepted a data object and just created an separate singleton manager object that handles saving/getting the data that I needed, which I understand is not good design. However, when I tried passing the data back and forth between controllers like Apple suggests(ie setting properties on the controller as in the above code) it just became a confused mess and I feel like there's a better way to do this.
So any suggestions on this would be appreciated thanks.
I have a UIViewController which displays a table of data that is pulled from an online database. I have a singleton manager to handle the pulling of this data and provide the data the table needs.
This is an example of how the manager works:
#property (nonatomic) NSArray *dataArray;
...
- (void)refreshDataSource
{
[AClass fetchInBackgroundWithCompletionHandler:^(NSArray *objects) {
self.dataArray = [NSArray arrayWithArray:objects];
}
}
...
- (NSArray *)tableViewDataSource
{
return self.dataArray;
}
The view controller requests an update by calling -refreshDataSource in -viewDidLoad but in the meantime provides its UITableView with cache data from the manager by pointing to -tableViewDataSource.
When the view controller presents itself for the first time, everything is fine. The second time I go to present the same view controller, the app hangs. The network request doesn't fire either.
The only fix I've found is moving my -refreshDataSource call to -viewDidAppear: instead. But it itches me why this would be happening and discomforts me that something here must be wrong.
If anyone could provide any help or suggestions that would be great!
Your question hasn't explained everything, but here are a couple of ideas that might help you.
1) viewDidLoad is only called the first time your view loads. If you switch to a different view, then return to your tableView, refreshDataSource will not be called.
2) viewDidLoad might be firing before an array has been allocated and initialised, so it's nil when you're refreshing the data, whereas viewDidAppear might not have the same problem.
I can't give a more concrete answer without more information. Can you explain "the first time, everything is fine. The second time I go..." more clearly? Step-by-step what you do, if possible.
Can anyone tell me how I can phrase an if () statement to discover if a segue's destination view controller will appear in the Detail Split or in the Master Split?
I want to put the if() statement inside my prepareForSegue:sender: methods.
EDIT
All my detail views that are relevant to this question (at the moment) conform to a protocol and I am currently performing introspection on the destination controller using:
if ([segue.destinationViewController conformsToProtocol:#protocol(myProtocol)])...
I can see that this would not work if I wanted:
To be able to show the same class in either Master or Detail of the splitView from time to time, and at the same time...
I only want the if() statement to be true when the view is to be presented in the detail split.
Things like segue.destinationViewController.navigationController == ... don't appear to be any use either.
I was hoping that since we need to set "Master Split" or "Detail Split" when we set the segue up... there would be a way to access that information less circuitously.
SECOND EDIT:
The way I have this set up with using introspection does "work". It just doesn't seem very "Object Oriented". I don't think I should be querying the View Controller at all for this information, I can't see why the VC should know anything about which side of the splitView it will be displayed. Surely the object that should hold onto this information is the Segue and, as I say, it appears this is being "set" in the storyboard when we select "Detail" or "Master" split.
Maybe it isn't a property of anything, but I can't see how to get at it.
I suppose I could query the destinationViewController in its viewWillAppear to discover which NavigationController it is in after it appears on screen but, again, it seems a bit "hacky".
There is probably a better more abstract and reusable way to do this that I'm not aware of, but here is a suggestion that could help in your specific project that requires just a bit of special knowledge of your specific project.
If you use introspection in your prepare for segue, you can check to see if methods exist by using the responds to approach.
So for example, in typical implementations of a splitview controller (note - not all) the detail view will implement the methods to handle rotation. So if this is true in your project, you could do something like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController respondsToSelector:#selector(splitViewController:shouldHideViewController:inOrientation:)]) {
//do something
}
}
You could use this same approach based upon something that was unique but constant in your project related to either the master or detail view.
hope that helps,
be well
My experience is a little limited, but most times I've seen prepareForSegue used, the if() block checks segue.identifier to do anything that needs to be done specifically to handle building the new page. If you set the identifier for all your segues, you could just have code to handle each segue from that controller, and change what the code is depending on if that segue goes to a masterViewController or a detailViewController. Not really a well automated way, but it'll get the job done.
EDIT: oh geez, that wording is kinda confusing. If you want me to I can put a code example up, but it'll have to wait until Monday, as I don't have access to a Mac until then.
The talk of classes and protocols gave me another idea, but again, not sure if it will work - I wanted to test it before posting, but I'm not going to have the time to test anytime soon.
I think you should be able to create 2 new classes, UIMasterViewController and UIDetailViewController, that are subclasses of just UIViewController. Then, for each of your actual screens, instead of making them subclasses of UIViewController directly, make them either a UIDetailViewController or UIMasterViewController. Then, in your prepareForSegue,
if ([segue.destinationViewController isKindOfClass:UIMasterViewController])
{
//do master view specific stuff
}
else if ([segue.destinationViewController isKindOfClass:UIDetailViewController])
{
//do detail view stuff here
}
This should be a pretty dependable way to tell where your segue is sending you, as long as you can set up the custom view controller classes right. This still won't solve the first issue noted in the question
"To be able to show the same class in either Master or Detail of the
splitView from time to time, and at the same time..."
This could be overcome by making 2 copies of all of the views you want to be able to show as either or both, then make one a UIMasterViewController and the other a UIDetailViewController - copy-paste should be good for most of the rest.
Let me know if this works - I'm not exactly sure how to set up the controllers off the top of my head, but I'm pretty sure it could be done. If it can, I can see this being a very useful thing.
this is my first post on here, though with the help of many questions and answers from members of this community, I have brought my project to near completion.
I have read multiple threads similar to what I'm asking, but the methods were completely different. No code has worked so far.
Basically (I say this because my code involves a lovely snake-like descent into a complicated mess, but applicable snippets will be put up upon request), my problem is that I'm calling
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
and it pushes my viewcontroller in the simulator and NSLogs the string I need changed beautifully, but it pushes a blank view! The code for that run makes the view controller variable a constant:
UIViewController *viewController = [[xSheetMusicViewController alloc]initWithNibName:nil bundle:nil];
So I thought to myself, what am I doing!? So I went back to the old method, which involved making the UIViewcontroller an if-then, if-else-then statement that would push different views depending on whether certain rows were selected (standard stuff). Which pushed a new view with my string loaded perfectly, but it only NSLog'ed one string over and over! And the worst part was the my app would call either SIGABRT, or EXC_BAD_ACCESS when I tried returning to the rootviewcontroller. (here's the applicable code):
UIViewController *viewController = [[[UIViewController alloc]init]autorelease];
if (indexPath.row == 0 && indexPath.section == 0) {
appDelegate.baseURL = #"mussette.pdf";
viewcontroller = [[xSheetmusicViewController alloc]initwithnibname:nil bundle:nil];
}
else if (...)
//pushview, changestring, blah blah//
Now, I would prefer that my view push the PDF like it's supposed to, and have the correct string value (and not give me SIGABRT or EXC_BAD_ACESS, but those are givens), but it seems that compromise is just out of my reach. I know there's probably something stupid I'm doing that could be solved with one line of code, but for now, it seems hopeless.
Thanks in advance.
EDIT: To answer all of your questions, yes, there is no xib, rather an
(id)init
method in the next view.
EDIT 2: to answer lostInTransit's request and add some additional details:
<else if (indexPath.row == 1 && indexPath.section == 0) {
appDelegate.baseURL = #"Importing PDF's.pdf";
Also, if it helps, the output keeps logging:
Application tried to push a nil view controller on target .
When I try to push the view from a tableviewcell, and it did that before when it loaded the PDF right so I ignored it.
Question: why do you first initialize your viewController as a UIViewController and then again as xSheetmusicViewController? I think the problem is with releasing values properly. In one init, you do an autorelease, in the other you don't. So chances are you are releasing a variable twice leading to the BAD ACCESS.
Do you mind posting the "blah blah" :) in the last piece of code?
Do you have a file named xSheetmusicViewController.xib in your application? That will be loaded with your view controller as its owner after you call [[xSheetmusicViewController alloc] initNithNibName:nil bundle:nil]; (it will actually be loaded when the view property is first accessed). If that file doesn’t exist, then the view controller’s -loadView: method will be called to load its view.
If you have a blank view, either you have a blank or mis-named nib (perhaps you renamed the class but not the nib?) or you aren’t creating the right view in -loadView:.