I have just upgraded to XCode5 and iOS7 and now my application has stopped working.
I am creating a new view based on a property of a current view, and I need to set some properties of the new view before I display it.
Previously, I did it like this :-
hqView *v = [[hqView alloc] initWithNibName:NULL bundle:NULL];
[v setProperty1:true];
[v setProperty2:false];
[self presentViewController:v animated:TRUE completion:NULL];
This then triggered the [viewDidLoad] method on the view controller, which had the following code in it :-
if ([self property1])
{
[list1 load]
}
else
{
[list2 load]
}
However now the [viewDidLoad] method is triggering as soon as I create the view, meaning that I am not able to set the properties before [viewDidLoad] is called and so it ALWAYS loads list2 regardless of what I actually want.
The thing is - this did NOT happen under iOS6, so I am wondering whether it is a new setting in XCode5 that has caused this to change, or if I am going to have to rewrite it to do what I need it to do?
You cannot know when viewDidLoad, viewWillAppear, etc... will be called.
My advice : Make a dedicated init method to your controller, something like :
#implementation hqView
- (instancetype)initWithProperty1:(BOOL)prop1 property2:(BOOL)prop2
{
// uses default NIB
self = [super initWithNibName:nil bundle:nil];
if (self){
[self setProperty1:prop1];
[self setProperty2:prop2];
}
return self;
}
#end
Set a breakpoint on your viewDidLoad method that is being called before your init method and you will be able to see what is causing the viewDidLoad to be called. you will probably find that it is being called because the view was referenced by some other code. this most often happens in a super class (like if you have a UIViewController superclass that implements common functionality for your view controllers). for example, if you accidentally put new code in that accessed self.view in a method in your superclass such as - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
you would notice this behavior. so make sure you are not accessing the view in any code before you wanted to. -rrh
Related
I am setting up a block which gets called on a custom UIButton press. I am assigning the block to the UIButton instance in viewDidLoad().
- (void) viewDidLoad{
[super viewDidLoad];
_customBTN.block = ^{
self.test = #"Something";
}
}
Should I keep the block on the stack since the block can only get called on a button press and that means viewDidLoad() would be on the stack, and this can be considered performant/best practice ... or am I doing something wrong?
Blocks don't stay on stacks (memory stack) and rather are copied (Objc objects referred inside block get a retain call (+1 retainCount) and scalar variables get copied) to heap when instantiated. It means when the line:
_customBTN.block = ^{
self.test = #"Something";
};
is executed, stack frame created for viewDidLoad function got popped from the stack, self got a +1 retainCount and block was assigned to the block property of _customBTN, later on when _customBTN calls the block (on say button tapped event) the block is executed and it uses self and does what it does.
P.S. Its safe to use weak references to self when referring inside a block. weak wouldn't increase retainCount of self (which can lead to a retain cycles in worse cases).
You can do it like this if there is no other choice. Also, do not use self in block. Create a week reference like this:
__weak typeof(self) weakSelf = self;
And use it in blocks:
_customBTN.block = ^{
weakSelf.test = #"Something";
}
I have a WKInterfaceController and I added a table as following:
// .h
#interface InterfaceController : WKInterfaceController
#property (weak, nonatomic) IBOutlet WKInterfaceTable *table;
#end
// .m
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
NSLog(#"did select");
}
- (void)table:(WKInterfaceTable *)table
didSelectRowAtIndex:(NSInteger)rowIndex{
NSLog(#"did select new");
}
However neither of the two methods gets called. I was unable to find any protocol declaration for WKInterfaceTable and neither any delegate property on the table.
Is there something that I am missing here?
I found out that the method was never called because I had set a segue to be triggered on selection of the row on Interface builder.
It seems that that by having no delegation and table protocols once you set a delegate it stops the didSelectRow method from being called.
In Apple's WKInterfaceController document it states that if you do not have any actions or segues then the method called is:
- table:didSelectRowAtIndex:
If you use segues then the methods called are:
For buttons:
- contextForSegueWithIdentifier:
For tables:
- contextForSegueWithIdentifier:inTable:rowIndex:
Swift 4
Here’s an example of selecting a WKInterfacetable row in a REST/JSON implementation.
Create a context property instance of the array class instead of using self.pushController.
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
let message = messageObjects[rowIndex]
presentController(withName: "MessageView", context: message)
}
I'm working on a demo app with iBeacons. At this moment I have the ranging for the beacons working in my firstviewcontroller, when I want to show a detailed view of the beacon data in a second view controller I do hear the the audible feedback on beacon change which means the loop of detecting closest beacons is still running in view controller 1 ... but how can I get updates from view controller 1 in my second view controller?
I tried passing them in the segue, but then it is static NSString data, can anyone help me on how to get the "live" data in the second view controller?
If you want to process ranged iBeacon data in more than one ViewController, you can set up ranging in your AppDelegate, then call public methods on each ViewController from there. The main didRangeBeacons: inRegion: in your AppDelegate can call the custom methods on each ViewController if that ViewController has been activated.
- (void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
if (self.firstViewController != Nil) {
[self.firstViewController handleBeacons: beacons];
}
if (self.secondViewController != Nil) {
[self.secondViewController handleBeacons: beacons];
}
}
In order to do this as above, you must maintain properties for each ViewController on your AppDelegate:
#interface MyAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) FirstViewController *firstViewController;
#property (strong, nonatomic) SecondViewController *secondViewController;
#end
You can populate them from each ViewController like this:
- (void)viewDidLoad {
[super viewDidLoad];
MyAppDelegate *appDelegate = (MyAppDelegate) [[UIApplication sharedApplication] delegate];
appDelegate.firstViewController = self;
}
I have a button which calls a method on the same view. That button generates a data and takes me to another MapViewControlelr .I have 'prepareForSegue' method on the first view. But that prepareForSegue is called first right after button click. As a result my variables aren't getting updated from the method call. Any idea whats wrong?
I think you should use GCD to load your data asynchronously and then perform the segue, something as follows:
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_release(queue);
dispatch_async(queue, ^(void) {
// Generate data
// Main queue
dispatch_async(dispatch_get_main_queue(), ^(void) {
// perform segue
[self performSegueWithIdentifier: #"MySegue" sender: self];
});
});
Also, instead of configuring the segue from button to newViewController - configure it from mainViewController to newViewController and then use performSegueWithIdentifier method
I'm facing a problem with a view-based NSTableView running on 10.8 (target is 10.7, but I think this is not relevant).
I'm using an NSTableView, and I get content values for my custom NSTableCellView through bindings. I use the obejctValue of the NSTableCellView to get my data.
I added a button to my cell, and I'd like it to trigger some action when clicked. So far I have only been able to trigger an action within the custom NSTableCellView's subclass.
I can get the row that was clicked like this, using the chain:
NSButton *myButton = (NSButton*)sender;
NSTableView *myView = (NSTableView*)myButton.superview.superview.superview;
NSInteger rowClicked = [myView rowForView:myButton.superview];
From there I don't know how to reach my App Delegate or controller where the action is defined.
As I am using cocoa bindings, I do not have a delegate on the NSTableView that I could use to trigger my action.
Do you have any idea how I could talked back to controller ?
Many thanks in advance!
Although you are using bindings you can still set your controller as the delegate for your tableview in the interface builder.
I see that you already are able to access the table view from inside your cell. The next task must be simple, just set the table view delegate as the target for your button's action.
Thanks for your question, I also will be triggering an action from a button on a NSTableView. Your question helped to put me on the correct path.
First to address the your solution to finding which row number my NSTableView is on. I was able to find it without knowing the button, in my custom NSTableView I installed the following as a first attempt:
- (NSInteger)myRowNumber
{
return [(NSTableView*)self.superview.superview rowForView:self];
}
this works fine, however it is less than robust. It only works if you already know specifically how deep you are in the view hierarchy. A more robust and universal solution is:
- (NSInteger)myRowNumber
{
NSTableView* tableView = nil;
NSView* mySuperview = self;
do
{
NSView* nextSuper = mySuperview.superview;
if (nextSuper == nil)
{
NSException *exception =
[NSException exceptionWithName:#"NSTableView not found."
reason:[NSString stringWithFormat:#"%# search went too deep.",
NSStringFromSelector(_cmd)] userInfo:nil];
#throw exception;
}
if ([nextSuper isKindOfClass:[NSTableView class]])
tableView = (NSTableView*)nextSuper;
else
mySuperview = mySuperview.superview;
} while (tableView == nil);
return [tableView rowForView:self];
}
this not only works at the NSTableView level, but works with anything installed at any level above it, no matter how complex the view hierarchy.
As to the unanswered part of your question, I established an IBOutlet in my class and using interface builder tied if to my files owner (in my case my document class). Once I had a reference to the class I was sending my message to, and the row number, I call the function. In my case the call required that I pass the row number it originates from.
[self.myDoc doSomethingToRow:self.myRowNumber];
I tested this and it works at various levels of the view hierarchy above NSTableView. And it functions without having to have the row selected first (which appears to be assumed in Apples documentation).
Regards, George Lawrence Storm, Maltby, Washington, USA
Use rowForView: and the responder chain
To respond to a control's action embedded within an NSTableCellView, the control should issue the action to the First Responder. Alternatively, File Owner is possible but this is more tightly coupled.
Use rowForView: within the action method to determine which row's control issued the action:
- (IBAction)revealInFinder:(id)sender {
NSInteger row = [self.tableView rowForView:sender];
...
}
The action is implemented within any of the responder chain classes. Most likely, this will be your subclassed NSWindowController instance. The responder could also be the application delegate; assuming the delegate has a means to talk to the NSTableView.
See Apple's example TableViewPlayground: Using View-Based NSTableView and NSOutlineView to see this in action.
Suhas answer helped me.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "EDIT_CELL_VIEW"), owner: self) as? SymbolManagerCell {
if let editButton = cell.subviews[0] as? NSButton {
editButton.target = cell // this is required to trigger action
}
return cell
}
return nil
}