When an item is selected by the user, I'm trying to pass the selected item's index along with a reference to the records controller to a UINavigationController. The problem I'm having is that I'm not sure how to pass those variables properly. I was trying to avoid passing them 'the wrong way'. [EDIT: The application works perfectly if I save the items in static variables within AppDelegate]
Below I show the code that runs when the user selects an item, the code that runs in the navigation controller (which uses an incorrect way to pass the variables), and finally the code that runs on the view controller that indicates the variables are not being passed.
Here's the code for when the user selects an item:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let storyboard = storyboard, let controller = storyboard.instantiateViewController(withIdentifier: "DetailNavigationController") as? DetailNavigationController else {
print("ERROR: didn't get the view controller we expected")
return
}
print("initializing the newly created page view controller with index \(indexPath.row)")
controller.bIndexPath = indexPath
controller.bController = bController
present(controller, animated: true, completion: nil)
}
.
That causes the `DetailNavigationController.viewDidLoad()` to run:
override func viewDidLoad() {
super.viewDidLoad()
print("DetailNavigationController vdl and index \(bIndexPath.row)")
guard let storyboard = storyboard, let controller = storyboard.instantiateViewController(withIdentifier: "ManagePageViewController") as? ManagePageViewController else {
print("ERROR: didn't get the view controller we expected")
return
}
controller.bIndexPath = bIndexPath
controller.bController = bController
}
.
The output from that proves that the data is being passed this far:
DetailNavigationController vdl and index 4
But in ManagePageViewController we apparently don't get the same instance, because the variables are not set. I "knew this wouldn't work", but didn't know what else to try.
So below we see the ManagePageViewController.viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad ManagePageViewController \(bIndexPath)")
if let viewController = viewPhotoCommentController(currentIndex ?? 0) {
if let bController = bController {
viewController.bController = bController
} else {
print("ManagePageViewController viewDidLoad bController.performFetch")
let bSortedByName = SaucerItem.order(Column("name").asc, Column("name"))
bController = try! FetchedRecordsController(dbQueue, request: bSortedByName)
try! bController.performFetch()
vController.brewController = bController
}
let viewControllers = [viewController]
print("setting a view controller instance with currentIndex \(currentIndex ?? 0)" )
setViewControllers(viewControllers, direction: .forward, animated: false, completion: nil)
}
dataSource = self
}
.
And the output that shows going down the path of making new `bController`:
viewDidLoad ManagePageViewController nil
ManagePageViewController initializing the newly created page view controller with index 0
ManagePageViewController viewDidLoad bController.performFetch
setting a view controller instance with currentIndex 0
.
So that indicates the variables set earlier must have been on some other instance, so that's the wrong approach.
I thought I could make the settings in AppDelegate, but that just seemed wrong to me. There must be a way to pass the data cleanly. Some other things I tried included using the facilities contained within the UINavigationController to figure out what it was going to run, and set the variables on that instance, but I wasn't able to discover the instance that the controller was going to run.
Additional Information:
In order to answer a question about why it seems like I'm going from "A" to "C" through "B", I'll place a diagram here and try to explain why. The bottom line is that it must be configured this way to enable the paging on the detail screen.
Printing controller from DetailNavigationController.viewDidLoad() said ManagePageViewController, and printing viewController from ManagePageViewController.viewDidLoad() said PageViewController.
So what's the right way to pass this information to the next view controller?
Related
I have read Access property delegate in Kotlin which is about accessing a delegate from an instance. One can use KProperty::getDelegate since Kotlin 1.1, however this will return the instance of the delegate and therefore needs an instance of the class first.
Now I want to get the type of the delegate without having an instance of the class. Consider a library with a custom delegate type CustomDelegate that want's to get all properties of a class that are delegated to an instance of CustomDelegate:
class Example
{
var nonDelegatedProperty = "I don't care about this property"
var delegatedProperty1 by lazy { "I don't care about this too" }
var delegatedProperty2 by CustomDelegate("I care about this one")
}
How can I, given I have KClass<Example>, but not an instance of Example, get all properties delegated to CustomDelegate?
How can I, given I have KClass<Example>, but not an instance of
Example, get all properties delegated to CustomDelegate?
You can do it in two ways depending on your needs.
First of all, you have to include the kotlin-reflect dependency in your build.gradle file:
compile "org.jetbrains.kotlin:kotlin-reflect:1.1.51"
In my opinion, you should use the first solution if you can, because it's the most clear and optimized one. The second solution instead, can handle one case that the first solution can't.
First
You can loop an the declared properties and check if the type of the property or the type of the delegate is CustomDelegate.
// Loop on the properties of this class.
Example::class.declaredMemberProperties.filter { property ->
// If the type of field is CustomDelegate or the delegate is an instance of CustomDelegate,
// it will return true.
CustomDelegate::class.java == property.javaField?.type
}
There's only one problem with this solution, you will get also the fields with type CustomDelegate, so, given this example:
class Example {
var nonDelegatedProperty = "I don't care about this property"
val delegatedProperty1 by lazy { "I don't care about this too" }
val delegatedProperty2 by CustomDelegate("I care about this one")
val customDelegate = CustomDelegate("jdo")
}
You will get delegatedProperty2 and customDelegate. If you want to get only delegatedProperty2, I found an horrible solution that you can use if you need to manage this case.
Second
If you check the source code of KPropertyImpl, you can see how a delegation is implemented. So, you can do something like this:
// Loop on the properties of this class.
Example::class.declaredMemberProperties.filter { property ->
// You must check in all superclasses till you find the right method.
property::class.allSuperclasses.find {
val computeField = try {
// Find the protected method "computeDelegateField".
it.declaredFunctions.find { it.name == "computeDelegateField" } ?: return#find false
} catch (t: Throwable) {
// Catch KotlinReflectionInternalError.
return#find false
}
// Get the delegate or null if the delegate is not present.
val delegateField = computeField.call(property) as? Field
// If the delegate was null or the type is different from CustomDelegate, it will return false.
CustomDelegate::class.java == delegateField?.type
} != null
}
In this case, you will get only delegatedProperty2 as result.
I have created a table in which the items contains some groups and some labels. I would like that when the user tap on the item, I receive an action. How can I do it? Is it possible to add an invisible button covering all the area of an item of the table?
You will need to connect the table row to another Interface Controller by control dragging from the row, and selecting push or modal.
Give the Storyboard Segue an identifier (string).
Then, to pass data to the Interface Controller, override contextForSegueWithIdentifier:inTable:rowIndex:
Swift:
override func contextForSegueWithIdentifier(segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex: Int) -> AnyObject? {
if segueIdentifier == "identifier" {
return "banana"
}
return nil
}
Objective-C:
- (nullable id)contextForSegueWithIdentifier:(nonnull NSString *)segueIdentifier inTable:(nonnull WKInterfaceTable *)table rowIndex:(NSInteger)rowIndex {
if ([segueIdentifier isEqualToString:#"identifier"]) {
return #"banana";
}
return nil;
}
Button can contain groups (you have to choose this option in the editor) so the problem is solved! I don't understand why this answer is rated negatively. It works!
I have a table in the 1st interface controller , when a press on a row , a modal interface controller opens up , it contains a button.
I want the button to delete the row in the first interface controller.
Here is my code :
In the first interface controller
Blockquote
// It opens up a modal view ( with the context of the tapped row )
override func contextForSegueWithIdentifier(segueIdentifier: String, inTable table: WKInterfaceTable, rowIndex: Int) -> AnyObject? {
var timelineRow = timeline.reverse()
return timelineRow[rowIndex]
}
Blockquote
And here is my code in the second interface controller
Blockquote
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
sentContext = (context as? Dictionary)!
sentRow = sentContext
//sentRow contains the context
}
#IBAction func deleteRow() {
var sentRow : [String:String] = ["action":"delete"]
NSNotificationCenter.defaultCenter().postNotificationName("notification_DeleteRow", object: nil, userInfo: sentRow)
dismissController()
}
Blockquote
I've sent the index of the row through the contextForSegueWithIdentifier.
In the 2nd Interface Controller I've extracted the Context and put it in variable
I then send back the userInfo throught the NSNotificationCenter
My Problem :
How can I use the userInfo sent back from the modal controller in order to delete the tapped row.
How would I manage to delete the tapped row (1st IC) by pressing on the delete button situated in the (2nd IC)
There are a few options in this situation:
You could use NSUserDefaults, and while it would work, this isn't how that class is intended to be used.
You can create your own custom NSNotification and broadcast it from the modal controller. Your first interface controller would listen for this event and delete the appropriate record.
You can pass a reference to your first interface controller to the modal controller and retrieve it in awakeWithContext:. This allows you to set the first interface controller as a delegate. Once this happens, you can define whatever protocol you'd like to inform the first controller of important events.
I have a blog post that goes into more detail on the second two topics: Advanced WatchKit Interface Controller Techniques
This can be achieved with custom delegate easily,
#protocol MyCustomDelegate <NSObject>
- (void)deleteButtonTapped:(id)sender;
#end
- (IBAction)deleteButtonTapped:(id)sender {
if ([self.delegate respondsToSelector:#selector(deleteButtonTapped:)]) {
[self.delegate deleteButtonTapped:sender];
};
}
More detailed answer is here.
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
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
}