Get NSTextField value from NSWindow beginSheet completion handler - objective-c

I created a window that slides down from the top using the beginSheet method, on the window is some NSTextFields where a user can enter data and then when the user presses the ok button to provided the completion handler NSModalResponsOK, how can I get the values from the NSTextField in the NSWindow Sheet action to the view that called the beginSheet method. I created an initMethod that passes in the current View controller object to the NSWindow sheet modal so that I could try to set the parent view controller data properties. I can get the data showing in the completion handler block, but cannot get the values out of the completion handler block.
here is the action to show the sheet
- (IBAction)addJobDescription:(id)sender {
NSLog(#"fired add job desc");
self.addJobDescriptionViewController = [[AddJobDescriptionViewController alloc]initWithWindowNibName:#"AddJobDescriptionViewController" andWithViewController:self];
[[[self view] window] beginSheet:self.addJobDescriptionViewController.window completionHandler:^(NSModalResponse returnCode){
NSLog(#"%ld",(long)returnCode);
if (returnCode == 1) {
[self setJobDescriptionTableDataWithNumber:self.jobDescriptionNumberArray andWithDescription:self.jobDescriptionArray andTotal:self.JobDescriptionTotalArray];
NSLog(#" first job description: %#",self.jobDescriptionArray[0]);
}
}];
}
Here is the dismiss action from the NSwindow instance that has the sheet view and NSTextFields
- (IBAction)endSheetView:(id)sender {
self.nEstimateViewController.jobDescriptionArray = [[NSArray alloc]initWithObjects:self.jobDescriptionOne.stringValue,self.jobDescriptionTwo.stringValue,self.jobDescriptionThree.stringValue,self.jobDescriptionFour.stringValue, nil];
self.nEstimateViewController.JobDescriptionTotalArray = [[NSArray alloc]initWithObjects:self.descriptionTotalOne.stringValue,self.descriptionTotalTwo.stringValue,self.descriptionTotalThree.stringValue,self.descriptionTotalFour.stringValue, nil];
self.nEstimateViewController.jobDescriptionNumberArray = [[NSArray alloc]initWithObjects:#"1",#"2",#"3",#"4", nil];
[self.window.sheetParent endSheet:self.window returnCode:NSModalResponseOK];
}
I have tried to make a method to call from the completion block but no dice. I want to get the data from the NSTextFields on the NSWindow Sheet view to add to an array to update a NSTableView. I am going crazy trying everything but I havent been able to find a solution. I am thinking I need the __block some where or make the parent view controller a delegate of the NSWindow sheet instance.
Let me know if you need any other info or code.

Related

Using the segue from master detail application with custom button.

So, I have a master detail application. So far an object is created at launch and added to the master list, the detail view works fine as well.
What I would like to do, is to let the user add object to the master list by pressing a button. I don´t need a new view for that though, I want to use the standard Detail View for this part.
When the user taps the "Add" button, a new object should be created and then go into the detailed view of that object. I thought, "hey, why not use the standard segue as well?"
So I added a Bar Button Item to the MasterView.
This my code from MasterViewController.m:
-(IBAction)addNewItem:(UIStoryboardSegue *)segue{
if([[segue identifier] isEqualToString:#"showDetail"]){
CoolItem *newItem;
NSDate *today = [NSDate date];
newItem = [[CoolItem alloc]initWithDate:today];
[self.dataController addCoolItemWithItem:newItem];
DetailViewController *detailViewController = [segue destinationViewController];
detailViewController.coolItem = newItem;
}
}
"showDetail" is the identifier of the standard segue that comes with the master detail template.
The result: Nothing! The method is not called at button tap.
I´ve made sure my button is connected to this method, (it does however shows up as a unwind segue in the document outline).
Any ideas on what goes wrong?
I'm not sure what went wrong but I was able to achieve the desired functionality using the following code:
-(void)viewDidload {
...
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(Add:)];
}
-(IBAction)Add:(id)sender
{
// Create the new item and set it as active
...
[self performSegueWithIdentifier: #"SegueName" sender:self];
}
Hope this helps
Yaron

UIViewController and UIImagePickerController: Unable to create and managing views as expected

I have a UIViewController subclass that contains an instance of UIImagePickerController. Let's call this controller CameraController. Among other things, the CameraController manages the UIImagePickerController instance's overlayView, and other views, buttons, labels etc. that are displayed when the UIImagePickerController, let's call this instance photoPicker, is displayed as the modal controller.
The photoPicker's camera overlay and the elemets that are part of the CameraController view hierarchy display and function as expected. The problem I'm having is that I cannot use UIViewController's default initializer to create the CameraController's view heirarchy.
I am initializing CameraController from within another UIViewController. Let's call this controller the WebViewController. When the user clicks on a button in a view managed by WebViewController, the launchCamera method is called. It currently looks like this:
- (void) launchCamera{
if (!cameraController) {
cameraController = [[CameraController alloc] init];
// cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
// bundle:[NSBundle mainBundle]];
cameraController.delegate = self;
}
[self presentModalViewController:cameraController.photoPicker animated:NO];
}
I want to be able to create CameraController by calling initWithNibName:bundle: but it's not working
as I'll explain.
CameraController's init method looks like this:
- (id) init {
if (self == [super init]) {
// Create and configure the image picker here...
// Load the UI elements for the camera overlay.
nibContents = [[NSBundle mainBundle] loadNibNamed:#"CameraController" owner:self options:nil];
[nibContents retain];
photoPicker.cameraOverlayView = overlay;
// More initialization code here...
}
return self;
}
The only way I can get the elements to load from the CameraController.xib file is to call loadNibNamed:owner:options:. Otherwise the camera takes over but no overlay nor other view components are displayed. It appears that a side-effect of this problem is that none of the view management methods on CameraController are ever called, like viewDidLoad, viewDidAppear etc.
However, all outlets defined in the nib seem to be working. For example, when the camera loads a view is displayed with some instructions for the user. On this view is a button to dismiss it. The button is declared in CameraController along with the method that is called that dismisses this instructions view. It is all wired together through the nib and works great. Furthermore, the button to take a picture is on the view that servers as photoPicker's overlay. This button and the method that is called when it's pressed is managed by CameraController and all wired up in the nib. It works fine too.
So what am I missing? Why can't I use UIViewController's default initializer to create the CameraController instance. And, why are none of CameraController's view mangement methods ever called.
Thanks.
Your problem is easy but need some steps.
Well... First, if overlay is an IBOutlet, it can not be loaded at init time. So move picker and co in viewDidLoad. Place also here all other items that your say that they are not loaded. They should be loaded there (viewDIDLoad). Check that outlets are connected.
Second, call
cameraController = [[CameraController alloc] initWithNibName:#"CameraController"
bundle:nil];
and ensure that CameraController contains (just) a view, and CameraController inherits UIViewController. Check also file's owner.
And at some time, you may consider that calling :
[self presentModalViewController:cameraController.photoPicker animated:NO];
does not make the CameraController control your picker. Does that make sense to you ?
What does that do regarding your problem ?
It seems you are confusing some things. I try to explain in another way :
The one that controls the picker is the one that is its delegate. Your may consider creating in a MAIN view.
The controller of the overlay (added as subview) is the one that own its view in File's Owner. That may be created from the MAIN view, adding its view as subview of the controller. Basically, it is loaded just to get the overlay, but viewDidLoad, ... won't be called.
That's all and I belive those steps are not ok in your code.
That should give something like :
MainController
Loadcamera {
self.picker = [UIImagePicker alloc] init.....];
self.picker.delegate = self;
SecondController* scnd = [[SecondController alloc] initWithNibName:#"SecondController" bundle:nil];
[self.picker addOverlay:scnd.view];
[self presentModalViewController:self.picker animated:NO];
}
/// And here manage your picker delegate methods
SecondController
// Here manage your IBActions and whatever you want for the overlay

Opening a new window and waiting for it to close

I have a Mac OS X app written in objetive-c Cocoa. You can see most of the code in this previous question. Essentially you click a button on the main window (the app delegate) and it opens another window where the user can enter information.
In the following code (that gets called when the user press the button in the app's main window)
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow showWindow:self];
NSLog(#"this is a log line");
}
The NSLog line gets printer immediately after I called showWindow. Is there any way to wait until controllerWindow is closed to continue with the NSlog?
The reason for this is that the user set's a value on the new window I opened and I need to collect that value on the same OnLaunch so I need to wait.
I know that modal windows are bad form in Mac, but I have no control over this feature.
I've tried with
[NSApp runModalForWindow:[controllerWindow window]];
and then setting the popup window to
[[NSApplication sharedApplication] runModalForWindow:popupwin];
and it works but then the focus never gets passed to the main window anymore
Thanks!
If you want the window to be modal for your application, use a sheet: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Sheets/Tasks/UsingCustomSheets.html
However, there is no way to suspend execution of a method while the sheet is displayed, this would be tantamount to blocking the current run loop. You would have to break you code into the begin and end methods as described in the linked documentation.
Here are the steps you need to follow:
In TestAppAppDelegate create an NSWindow outlet to hold your sheet and an action to dismiss the sheet
Create a nib with an NSWindow as the root object. I think you already have this in "pop". Set the Visible at Launch option to NO (this is very important)
Set the file's owner of this nib to TestAppAppDelegate and connect the window to your new outlet, and the close button to your new action
In your method to launch the sheet (OnLaunch), use the following code:
(ignore this it's to make the code format properly!)
if(!self.sheet)
[NSBundle loadNibNamed:#"Sheet" owner:self];
[NSApp beginSheet:self.sheet
modalForWindow:self.window
modalDelegate:self
didEndSelector:#selector(didEndSheet:returnCode:contextInfo:)
contextInfo:nil];
Your close button action should be [NSApp endSheet:self.sheet];
Your didEndSheet: method should be [self.sheet orderOut:self];
You can use UIVIew method animateWithDuration:delay:options:animations:completion: to accomplish this.
You said you want the next line to execute once the window is closed, rather than after it is opened. In any case, you may end the OnLaunch method this way:
- (IBAction)OnLaunch:(id)sender {
MyClass *controllerWindow = [[MyClass alloc] initWithWindowNibName:#"pop"];
[controllerWindow animateWithDuration:someDelay:options: someUIAnimationOption
animations:^{
[controllerWindow showWindow:self]; // now you can animate it in the showWindow method
}
completion:^{
[self windowDidFinishShowing]; // or [self windowDidFinishDisappearing]
}
}
- (void) windowDidFinishShowing {
NSLog(#"this is a log line");
}

Trouble using custom modal sheet with Cocoa

My objective: Display a custom sheet with a determinate NSProgressIndicator while the app works through a lengthy loop. I want the sheet to be application-modal, not document-modal. The user cannot dismiss the modal sheet. They must wait for the app to finish processing the loop.
Problem: I can't get the custom sheet to attach to the window. It appears as a separate window lacking the window title bar (as a sheet should). Additionally, the sheet is not released (does not close) when the loop finishes.
I have 2 separate nib files for the sheet and main application window, and also 2 controller classes for each window.
Here's the pertinent information:
Controller implementation for the custom sheet:
#implementation ProgressSheetController //subclass of NSPanel
-(void)showProgressSheet:(NSWindow *)window
{
//progressPanel is an IBOutlet to the NSPanel
if(!progressPanel)
[NSBundle loadNibNamed:#"ProgressPanel" owner:self];
[NSApp beginSheet: progressPanel
modalForWindow: window
modalDelegate: nil
didEndSelector: nil
contextInfo: nil];
//modalSession is an instance variable
modalSession = [NSApp beginModalSessionForWindow:progressPanel];
[NSApp runModalSession:modalSession];
}
-(void)removeProgressSheet
{
[NSApp endModalSession:modalSession];
[NSApp endSheet:progressPanel];
[progressPanel orderOut:nil];
}
//some other methods
#end
Implementation for the main application window. testFiles method is an IBAction connected to a button.
#implementation MainWindowViewController //subclass of NSObject
-(IBAction)testFiles:(id)sender;
{
//filesToTest is a mutable array instance variable
int count = [filesToTest count];
float progressIncrement = 100.0 / count;
ProgressSheetController *modalProgressSheet = [[ProgressSheetController alloc] init];
[modalProgressSheet showProgressSheet:[NSApp mainWindow]];
int i,
for(i=0; i<count; i++)
{
//do some stuff with the object at index i in the filesToTest array
//this method I didn't show in ProgressSheetController.m but I think it's self explanatory
[modalProgressSheet incrementProgressBarBy:progressIncrement];
}
//tear down the custom progress sheet
[modalProgressSheet removeProgressSheet];
[modalProgressSheet release];
}
#end
One thought: Am I subclassing correctly? Should I use NSWindowController instead? Thank you in advance for your help!
Found this gem doing some googling. The behavior for my NSPanel in Interface Builder was set to "Visible at Launch" which is the default for new windows when using IB. What happened was as soon as the nib was loaded, the window was made visible BEFORE [NSApp beginSheet:...]. Therefore, unchecking the "Visible at Launch" option solved my problem and now a sheet appears connected to the window like I want.
#implementation ProgressSheetController //subclass of NSPanel
-(void)showProgressSheet:(NSWindow *)window
{
//progressPanel is an IBOutlet to the NSPanel
if(!progressPanel)
[NSBundle loadNibNamed:#"ProgressPanel" owner:self];
So your NSPanel owns another NSPanel? If the second one is the progress panel, what does the first one show?
Should I [subclass] NSWindowController instead?
Sounds like it.
modalSession = [NSApp beginModalSessionForWindow:progressPanel];
Why?
[modalProgressSheet showProgressSheet:[NSApp mainWindow]];
Have you verified that there is a main window? mainWindow does not return the window you care most about; it returns the active window (which is probably, but not necessarily, also key).
If mainWindow is returning nil, that could be the cause of your problem.
int i,
for(i=0; i<count; i++)
{
//do some stuff with the object at index i in the filesToTest array
First, int is the wrong type. You should use NSUInteger for NSArray indexes.
Second, unless you need the index for something else, you should use fast enumeration.

Move focus to newly added record in an NSTableView

I am writing an application using Core Data to control a few NSTableViews. I have an add button that makes a new a record in the NSTableView. How do I make the focus move to the new record when this button is clicked so that I can immediately type its name? This is the same idea in iTunes where immediately after clicking the add playlist button the keyboard focus is moved to the new line so you can type the playlist's name.
Okay well first of all, if you haven't already got one, you need to create a controller class for your application. Add an outlet for the NSArrayController that your objects are stored in, and an outlet for the NSTableView that displays your objects, in the interface of your controller class.
IBOutlet NSArrayController *arrayController;
IBOutlet NSTableView *tableView;
Connect these outlets to the NSArrayController and the NSTableView in IB. Then you need to create an IBAction method that is called when your "Add" button is pressed; call it addButtonPressed: or something similar, declaring it in your controller class interface:
- (IBAction)addButtonPressed:(id)sender;
and also making it the target of your "Add" button in IB.
Now you need to implement this action in your controller class's implementation; this code assumes that the objects you have added to your array controller are NSStrings; if they are not, then replace the type of the new variable to whatever object type you are adding.
//Code is an adaptation of an excerpt from "Cocoa Programming for
//Mac OS X" by Aaron Hillegass
- (IBAction)addButtonPressed:(id)sender
{
//Try to end any editing that is taking place in the table view
NSWindow *w = [tableView window];
BOOL endEdit = [w makeFirstResponder:w];
if(!endEdit)
return;
//Create a new object to add to your NSTableView; replace NSString with
//whatever type the objects in your array controller are
NSString *new = [arrayController newObject];
//Add the object to your array controller
[arrayController addObject:new];
[new release];
//Rearrange the objects if there is a sort on any of the columns
[arrayController rearrangeObjects];
//Retrieve an array of the objects in your array controller and calculate
//which row your new object is in
NSArray *array = [arrayController arrangedObjects];
NSUInteger row = [array indexOfObjectIdenticalTo:new];
//Begin editing of the cell containing the new object
[tableView editColumn:0 row:row withEvent:nil select:YES];
}
This will then be called when you click the "Add" button, and the cell in the first column of the new row will start to be edited.
I believe an easier and more proper way to do it is by implementing it this way.
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSTableView *tableView = [notification object];
NSInteger selectedRowIndex = [tableView selectedRow];
NSLog(#"%ld selected row", selectedRowIndex);
[tableView editColumn:0 row:selectedRowIndex withEvent:nil select:YES];
I.e.
Implement tableViewSelectionDidChange:(NSNotification *)notification
fetch the selected row index
Call editColumn:(NSInteger)column row:(NSInteger)row withEvent:(NSEvent *)theEvent select:(BOOL)select from there with the row index.
Important note: this solution will also trigger the editing when user will simply select a row. If you only want editing triggered when adding a new object, this is not for you.
Just create a separate #IBAction in your controller and invoke the NSArrayController.add method manually. After that you can select the row
#IBAction func addLink(_ sender: Any) {
// Get the current row count from your data source
let row = links.count
arrayController.add(sender)
DispatchQueue.main.async {
self.tableView.editColumn(0, row: row, with: nil, select: true)
}
}