How do I change a UIButtons label, label color etc. from another view controller? - view

In one view (main view) controller I have a UIButton that creates a new UIButton. When you make a long press on the new UIButton a new view controller (second view) is presented by presentmodalviewcontroller. In the second view I have a UITableView and in the first UITableViewCell there's a UITextField. What I want is that when you input something in the UITextField the new UIButtons title changes to that.
What I have done is to create a NSString in my app delegate. In the second view in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
I have used this code:
// Passing the TextFieldText to NewButton.
iAppAppDelegate *AppDelegate = (iAppAppDelegate *)[[UIApplication sharedApplication] delegate];
AppDelegate.ButtonText = [TextFieldText text];
iAppView = [[iAppViewController alloc] initWithNibName:#"iAppViewController" bundle:nil];
[TextFieldText addTarget:iAppView action:#selector(ApplyAllObjectsSettings) forControlEvents:UIControlEventEditingChanged];
In the main view I used this action to change the new UIButtons title:
- (void)ApplyAllObjectsSettings {
iAppAppDelegate *AppDelegate = (iAppAppDelegate *)[[UIApplication sharedApplication] delegate];
[NewButton setTitle:AppDelegate.ButtonText forState:UIControlStateNormal]; }
It does however not work. Any idea of how to make this work or another way to do it, would really be appreciated :)
Thanks in advance :)
Update:
In my second views .h file I have referred to the first view controller like this:
#class iAppViewController;
#interface ButtonSettings : UIViewController < UIPickerViewDataSource, UIPickerViewDelegate > {
// iAppViewController
iAppViewController *iAppView;
in the .m file I have imported the first view controller
#import "iAppViewController.h"
And this is the code I use to call the action:
[TextFieldText addTarget:iAppView action:#selector(ApplyAllObjectsSettings:) forControlEvents:UIControlEventEditingChanged];
And this is the action in the first view controller:
- (void)ApplyAllObjectsSettings:(id)sender {
[NewButton setTitle:((UITextField *)sender).text forState:UIControlStateNormal];
}

Your code isn't working because you never actually pass the new text to the button. When you say
AppDelegate.ButtonText = [TextFieldText text];
is set's the ButtonText (which I guess is an NSString) to the UITextFields current text, which is most likely empty. If you look at the docs or other examples, you will see that the selector for UIControlEventEditingChanged actually accepts one argument. This is the sender, so in your case, the UITextField which fires the event. Using it, you can access the entered text and you won't even need your ButtonText variable.
So change row where you set UITextViews target to this: (Notice the new ':')
[TextFieldText addTarget:iAppView action:#selector(ApplyAllObjectsSettings:) forControlEvents:UIControlEventEditingChanged];
Then change your listener to this
- (void)ApplyAllObjectsSettings:(id)sender {
[NewButton setTitle:((UITextField *)sender).text forState:UIControlStateNormal];
}
Update:
So as I said, the iAppView in your second view needs to point to the first view. One way of archiving this is by making it a property like this (in your ButtonSettings.h):
#interface ButtonSettings : UIViewController < UIPickerViewDataSource, UIPickerViewDelegate > {
...
iAppViewController *iAppView;
}
#property (nonatomic, strong) iAppViewController * iAppView;
And don't forget #synthesize iAppView; in your ButtonSettings.m file. Now, when you are creating the instance of ButtonSettings in your FIRST view, you can just pass the reference like this:
myButtonSettingsInstance.iAppView = self;

Related

Why won't the data display in my NSTableView(view based)?

I followed the advice here on how to setup a MainWindowController: NSWindowController for my project's single window. I used a Cocoa class to create the .h/.m files, and I checked the option Also create .xib for User Interface. As a result, Xcode automatically hooked up a window, which I renamed MainWindow.xib, to my MainWidowController.
Next, I deleted the window in the default MainMenu.xib file (in Interface Builder I selected the window icon, then I hit the delete key). After that, I was able to Build my project successfully, and my controller's window in MainWindow.xib displayed correctly with a few buttons on it.
Then I tried adding an NSTableView to my MainWindowController's window. In Xcode, I dragged the requisite delegate and datasource outlets for the NSTableView onto File's Owner, which is my MainWindowController, and I implemented the methods in MainWindowController.m that I thought would make the NSTableView display my data:
- tableView:viewForTableColumn:row:
- numberOfRowsInTableView:
Now, when I Build my project, I don't get any errors, but the data doesn't appear in the NSTableView.
My code is below. Any tips are welcome!
//
// AppDelegate.h
// TableViews1
//
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
...
//
// AppDelegate.m
// TableViews1
//
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) MainWindowController* mainWindowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setMainWindowCtrl:[[MainWindowController alloc] init] ];
[[self mainWindowCtrl] showWindow:nil];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MainWindowController.h
// TableViews1
//
#import <Cocoa/Cocoa.h>
#interface MainWindowController : NSWindowController
#end
...
//
// MainWindowController.m
// TableViews1
//
#import "MainWindowController.h"
#import "Employee.h"
#interface MainWindowController () <NSTableViewDataSource, NSTableViewDelegate>
#property (strong) NSMutableArray* employees;
#property (weak) IBOutlet NSTableView* tableView;
#end
#implementation MainWindowController
- (NSView*)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
Employee* empl = [[self employees] objectAtIndex:row];
NSString* columnIdentifier = [tableColumn identifier];
//The column identifiers are "firstName" and "lastName", which match my property names.
//You set a column's identifier by repeatedly clicking on the TableView until only
//one of the columns is highlighted, then select the Identity Inspector and change the column's 'Identifier' field.
NSString* emplInfo = [empl valueForKey:columnIdentifier]; //Taking advantage of Key-Value coding
NSTableCellView *cellView =
[tableView makeViewWithIdentifier:columnIdentifier
owner:self];
NSLog(#"The Table view is asking for employee: %#", [empl firstName]);
[[cellView textField] setStringValue:emplInfo];
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [[self employees] count];
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
Employee* e1 = [[Employee alloc] initWithFirstName:#"Joe" lastName:#"Blow"];
Employee* e2 = [[Employee alloc] initWithFirstName:#"Jane" lastName:#"Doe"];
[self setEmployees:[NSMutableArray arrayWithObjects:e1, e2, nil]];
//Test to see if the employees array was populated correctly:
Employee* e = [[self employees] objectAtIndex:0];
NSLog(#"Here is the first employee: %#", [e firstName]);
//I see the output: "Here is the first employee: Joe"
}
- (id)init {
return [super initWithWindowNibName:#"MainWindow"];
}
- (id)initWithWindowNibName:(NSString *)windowNibName {
NSLog(#"Clients cannot call -[%# initWithWindowNibName] directly!",
[self class]
);
[self doesNotRecognizeSelector:_cmd];
return nil;
}
#end
...
//
// Employees.h
// TableViews1
#import <Foundation/Foundation.h>
#interface Employee : NSObject
#property NSString* firstName;
#property NSString* lastName;
- initWithFirstName:(NSString*)first lastName:(NSString*)last;
#end
...
//
// Employees.m
// TableViews1
//
#import "Employee.h"
#implementation Employee
- (id)initWithFirstName:(NSString *)first lastName:(NSString *)last {
if (self = [super init]) {
_firstName = first; //I read that you shouldn't use the accessors in init methods.
_lastName = last;
}
return self;
}
#end
File's Owner(=MainWindowController) connections:
NSTableView connections:
Response to comments:
Here is why calling [self tableView] reloadData] at the end of -windowDidLoad, as suggested in the comments, didn't work:
My _tableView instance variable--created by my #property declaration in MainWindowController.m--doesn't point to anything; therefore calling:
[[self tableView] reloadData]
I think is equivalent to calling:
[nil reloadData]
which doesn't do anything.
I never assigned anything to the _tableView instance variable in the -init method, nor did I assign it a value by dragging an outlet somewhere in Interface Builder. To fix that problem, I selected MainWindow.xib (the controller's window) in the Project Navigator(left pane), and then in the middle pane(Interface Builder), I selected the cube representing the File's Owner(selecting the Identity Inspector in the right pane reveals that the File's Owner is the MainWindowController). Then in the right pane, I selected the Connections Inspector, and it revealed an outlet called tableView, which is the IBOutlet variable I declared in MainWindowController.m.
Next, I dragged from the tableView outlet onto the TableView in the middle pane:
Doing that assigns the NSTableView object to the _tableView instance variable that was created by my #property declaration in MyWindowControler.m:
#property (weak) IBOutlet NSTableView* tableView;
As an experiment, I disconnected the outlet, then commented out the #property declaration for tableview, and the tableView outlet no longer appeared in the Connections Inspector. Also, if I change the declaration from:
#property (weak) IBOutlet NSTableView* tableView;
to:
#property (weak) NSTableView* tableView;
...then the tableView outlet doesn't appear in the Connections Inspector. That experiment answered a couple of questions I had about whether I should declare a property as an IBOutlet or not: if you need to assign one of the objects in Interface Builder to one of your variables, then declare the variable as an IBOutlet.
Thereafter, calling [self tableView] reloadData] at the end of -windowDidLoad succeeds in populating the TableView. However, I have not seen any tutorials that call reloadData, and even Apple's guide does not do that.
So, I am still puzzled about whether calling -reloadData is a hack or it's the correct way to do things.
Without it, your table view sits there blissfully clueless about your
expectation that it should even bother asking its datasource for data.
I assumed that an NSTableView automatically queries its datasource when it is ready to display itself, and that my code needed to be able to provide the data at that time.
I don't see you sending -reloadData to your table view anywhere. Tacking it onto the end of -windowDidLoad would be a good place. Without it, your table view sits there blissfully clueless about your expectation that it should even bother asking its datasource for data.
For all it knows, the data is simply not ready / available, so why would it try? More importantly, when should it try? It'd be rather rude of it to try whenever it pleases, considering the UI may not have finished loading / connecting to outlets, or its datasource may be in a vulnerable state (like teardown during/after dealloc) and sending datasource requests may result in a crash, etc.
Two things:
1st, set some breakpoints on when you set your employees array in windowDidLoad vs. when the table first attempts to populate itself and your numberOfRowsInTableView implementation gets called. If the latter happens before the former, then you'll need to add a reloadData after you create your array.
2nd, I personally always use NSCell instead of NSViews for my tables, so I always implement objectValueForTableColumn in my table's datasource. So I'm not sure if there's something different you need to do when you use NSView objects and implement viewForTableColumn. Is there a reason you're not using NSCell?

how to add View controller to every item on a menu?

i'm sure it's pretty easy , but i'm spending hours to get it right .
I download this simple menu from github :
https://github.com/Antondomashnev/ADDropDownMenuView
the view controller looks like this :
- (void)addDropDownMenu{
ADDropDownMenuView *dropDownMenuView = [[ADDropDownMenuView alloc] initAtOrigin:CGPointMake(0, 20)
withItemsViews:#[[self dropDownItemWithTitle:NSLocalizedString(#"Item 1", #"")],
[self dropDownItemWithTitle:NSLocalizedString(#"Item 2", #"")],
[self dropDownItemWithTitle:NSLocalizedString(#"Item 3", #"")]]];
dropDownMenuView.delegate = self;
dropDownMenuView.separatorColor = [UIColor blackColor];
[self.view addSubview: dropDownMenuView];}
i'm adding 2 more view controllers to the storyboard.
how can I connect them with Item 2 and Item 3 ? ( so I will see a different View every time i'll click different item ) .
any help will be appreciated...
I've changed demo code to show you solution.
In ViewController.m file add private category with two properties
#interface ViewController ()<ADDropDownMenuDelegate>
#property (nonatomic, strong) NSArray *viewControllers;
#property (nonatomic, strong) ADDropDownMenuView *dropDownMenuView;
#end
Then you need to create set viewControllers array. For example i want to create two instances of ViewController2 class (clean UIViewController template) in viewDidLoad
ViewController2 *vc1 = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController2Blue"];
[self addChildViewController:vc1];
ViewController2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:#"ViewController2Yellow"];
[self addChildViewController:vc2];
self.viewControllers = #[vc1, vc2];
You have to add these view controllers as a child view controller to self. Don't forget about setting storyboardID for your view controllers in storyboard. In this case first view controller has ViewController2Blue storyboardID and second has ViewController2Yellow.
And the last thing to do is implementing ADDropDownMenuDelegate method
- (void)ADDropDownMenu:(ADDropDownMenuView *)view didSelectItem:(ADDropDownMenuItemView *)item
{
NSLog(#"%# selected", item.titleLabel.text);
for(UIViewController *viewController in self.viewControllers){
[viewController.view removeFromSuperview];
}
if([item.titleLabel.text isEqualToString:NSLocalizedString(#"Item 1", #"")]){
[self.view insertSubview:((ViewController2 *)self.viewControllers[0]).view belowSubview:self.dropDownMenuView];
}
else{
[self.view insertSubview:((ViewController2 *)self.viewControllers[1]).view belowSubview:self.dropDownMenuView];
}
}
In this method you remove all UIViewController's views from superview. And then according ADDropDownMenuItemView titleLabel text you add viewController's view to self view.

Why is NSViewController not binding representedObject?

In short: I bind an NSTextField to the File's Owner (the view controller) and Model Key Path of representedObject.firstName, but editing the text field does not change the firstName.
Here are more details. I have a simple program that does nothing but create an instance of Thing (a simple class with some properties), and ThingViewController. The controller has an associated .xib with a simple UI -- a couple text fields to bind to properties of the Thing.
#interface Thing : NSObject
#property (nonatomic, strong) NSString *firstName;
#property (nonatomic, strong) NSString *lastName;
#property (nonatomic) BOOL someBool;
#end
And in the app delegate...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *cv = self.window.contentView;
ThingViewController *vc = [[ThingViewController alloc]
initWithNibName:#"ThingViewController" bundle:nil];
theThing = [Thing new];
theThing.firstName = #"Rob";
vc.representedObject = theThing;
[cv addSubview:vc.view];
}
The ThingViewController.xib is simple:
And here is the binding for that first text field:
When I run, the text field does show "Rob", so it works in that direction, but then as I edit the text field, the firstName property of theThing does not change.
What am I doing wrong?
Edit: Here's a link to a zipped project file for the above code: https://drive.google.com/file/d/0B2NHW8y0ZrBwWjNzbGszaDQzQ1U/edit?usp=sharing
Nothing is strongly referencing your view controller (ThingViewController), other than the local variable in -applicationDidFinishLaunching:. Once that goes out of scope, the view controller is released and dealloc'ed. The view itself is still around, since it is a subview of your window's contentView.
Once your view controller is released/gone, the text field has no connection back to the Thing object so it is in effect calling [nil setValue:#"New first name" forKeyPath:#"representedObject.firstName"].
Add a strong reference to your view controller (e.g., an instance variable of your app delegate) and try it again.
#implementation AppDelegate {
Thing *theThing;
ThingViewController *vc;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSView *cv = self.window.contentView;
vc = [[ThingViewController alloc] initWithNibName:#"ThingViewController" bundle:nil];
theThing = [Thing new];
theThing.firstName = #"Rob";
vc.representedObject = theThing;
[cv addSubview:vc.view];
}

How to use Storyboard to make popover that can be used in code?

I'm building a collection of forms each of which contains several fields. Some of the fields are UITextFields that will display a date. I've created a new class called DatePickerTextField, a descendant of UITextField. When a DatePickerTextField is tapped I'd like for a UIDatePicker control to appear in a popover.
My question is how do I use the storyboard to implement the popover? I can do a segue when there is a specific, visible control in the scene. But how do I represent a generic popover in the scene that I can attach to any instantiated DatePickerTextField that becomes active?
You can create segue that is not connected to any control but I don't think that there would be way to specify anchor point for popover from code. Another option is to create ViewController that is not connected with any segue. When editing storyboard, create ViewController which will be placed in popover, select it and navigate to Utilities pane->Attributes Inspector. Set Size to Freeform, Status Bar to None, specify unique Identifier that will be used to instantiate ViewController from code. Now you can change the size of ViewController by selecting its View and navigating to Utilities pane->Size Inspector.
After that you can create popover from code:
- (IBAction)buttonPressed:(id)sender {
UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
[popover presentPopoverFromRect:anchor.frame
inView:anchor.superview
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
One caveat is that you need to hold reference to popover as ivar of your class, otherwise it'll crash because UIPopoverController would be released and deallocated after buttonPressed returns:
#interface MyViewController : UIViewController {
// ...
UIPopoverController *popover;
// ...
}
So, I had a similar issue, and in case others might benefit, I figured I'd share it, since I benefit so much from stackoverflow.
This solution allows you to set the anchor of a customizable popover segue. It also allows you to configure the segue to be modal or not (I could not find a way to prevent the segue by dimming the exterior context, so if someone knows how to do that, I would be interested in hearing it); this is accomplished by setting the passthrough views for the popover controller. I also added the capacity to specify a custom view, rather than the view of the source viewcontroller (since I needed this capacity); this portion is not critical to the solution.
DynamicPopoverSegue.h
#interface DynamicPopoverSegue : UIStoryboardPopoverSegue
#property BOOL isModal;
#property UIView* sourceView;
#property CGRect anchor;
#end
DynamicPopoverSegue.m
#implementation DynamicPopoverSegue
- (void)perform
{
if (!self.popoverController.popoverVisible)
{
UIViewController* dst = (UIViewController*)self.destinationViewController;
UIViewController* src = (UIViewController*)self.sourceViewController;
UIView* inView = _sourceView ? _sourceView : src.view;
self.popoverController.contentViewController = dst;
if (!_isModal)
{
[self.popoverController setPassthroughViews:[[NSArray alloc] initWithObjects:inView, nil]];
}
[self.popoverController presentPopoverFromRect:_anchor
inView:inView
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}
#end
Then you just set your segue to "Custom" in the storyboard, and set the segue class to "DynamicPopoverSegue". In my case, since I wanted to associate it with dynamic layers in a view, I could not set the anchor, so I created the segue by control clicking from the view controller icon in the bar beneath my view controller to the view controller I was using to present the popupover.
To call the popover segue:
[self performSegueWithIdentifier:#"MyPopoverSegue" sender:self];
And to configure the popover segue:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"MyPopoverSegue"])
{
DynamicPopoverSegue* popoverSegue = (DynamicPopoverSegue*)segue;
// set the anchor to wherever you want it to be
popoverSegue.anchor = _destinationFrame;
}
}
- (IBAction)pressItemChooseOprateRoom:(id)sender {
if (isPad){
// UIView *anchor = sender;
UIViewController *viewControllerForPopover =
[self.storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
_myPopover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerForPopover];
CGRect rc=[self getBarItemRc:sender];
[_myPopover presentPopoverFromRect:rc
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
[MLControl shared].popover =self;
// [self perfformSegueWithIdentifier:SEGUE_POP_OPERATEROOM sender:self];
}else{
[self iphoneOpenOperateRoomList];
/* [self performSegueWithIdentifier:#"iPhonePushOperateRoom" sender:self];
*/
}
}
-(void)iphoneOpenOperateRoomList{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *vc = [storyboard instantiateViewControllerWithIdentifier:#"OperateRoomList"];
// if (!index.showTabBar) {
// vc.hidesBottomBarWhenPushed = YES;
// }
[self.navigationController pushViewController:vc animated:YES];
}
Just used the answer from Jonnywho for my SWIFT project. In case you need it:
Here's the SWIFT version:
let anchor: UIView = sender
var viewControllerForPopover = self.storyboard?.instantiateViewControllerWithIdentifier("GameAboutViewController") as! UIViewController?
let popover = UIPopoverController(contentViewController: viewControllerForPopover!)
popover.presentPopoverFromRect(anchor.frame, inView: anchor, permittedArrowDirections: UIPopoverArrowDirection.Any, animated: true)
Add a UIView in the scene dock.
You can add it as a subview to any existing view on the view controller.
You can then toggle it's isHidden property as you require.
You can add multiple such subviews and create many such popups.
This technique will save you from setting up a new View Controller and using segues.

IOS: use navigation controller in a subview

In a view of my program I have a button and with this button I open a subview; this subview is a view with a tableview. I want go to another view when I push a row of the tableview so I want to make this with a navigation controller; how can I do this?
Your table view delegate will receive a tableView:didSelectRowAtIndexPath: message when you click on one of the table's rows.
You can put there your code to create the UINavigationController and push on to it your new view.
This sample code (from another answer of mine on S.O.) shows how you can do that:
UINavigationController* navigation = [[UINavigationController alloc] init];
iVkViewController *overviewViewController = [[iVkViewController alloc] init];
overviewViewController.title = #"First";
[navigation pushViewController:overviewViewController animated:NO];
This should help you getting things on track.
One side note: you might think of having a navigation controller from the very start, this would make your UI more "well-behaved", but this depends ultimately on your app requirements.
If UIViewController addSubView a view of UITableViewController,you want to push in the method of tableView:didSelectRowAtIndexPath,you should check if self.navigationController is nil. If it's nil,You probably should use
[self.parentViewController.navigationController pushViewController:controller animated:YES];
if self.parentViewController is also nil,Sometimes,you have to set a #property to point out the parentViewController In UITableViewController,like:
#property (nonatomic, weak) UIViewController *parentVC;
and in UIViewController:
UITableViewController *tableViewVC = [[UITableViewController alloc] init];
tableViewVC.parentVC = self;
In UITableViewController ,-tableView:didSelectRowAtIndexPath:
[self.parentVC.navigationController pushViewController:controller animated:YES];

Resources