Problem:
I can't get my UIButton to send my Class an Event call
Here is what I tried first:
I made a class which extends UIViewController, called Viewer
#import "Viewer.h"
#import "MainScreen.h"
#implementation Viewer
- (void)viewDidLoad
{
MainScreen* main = [[MainScreen alloc]init: self];
[main show];
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
then:
I created a class called MainScreen, 2 IBOutlets - singlePlayerButton and view
Viewer is an instance of UIViewController
buttonPressed has the return type of IBAction
MainScreen:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
{
IBOutlet UIButton* singlePlayerButton;
IBOutlet UIView* view;
Viewer* viewer;
}
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
}
- (IBAction)buttonPressed:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Then I created an NIB called MainScreenLayout.xib
Here is what it looks like:
As you can see, I have four buttons but the only one that is connected to anything is the Single-Player button I also have the UIView connected to view.
Now, I linked the action to the Single-Player button like this:
buttonPressed is my method that I want to have called when the button is pressed, so I ctrl-clicked on First Responder and then clicked on the circle next to buttonPressed and then dragged it on top of the Single-Player button. Then the next dialog window popped up and I clicked on Touch up Inside. Here is what the Received Actions window looks like:
Now, I thought at this point it should work. I ran it on my iPhone Simulator and pressed the button.
Just so there is no question of whither or not I'm competent, here is a picture of me actually clicking the button:
I got no output whatsoever. Then I went to my code and looked at the method. This is what it looks like:
as you can see, the oval is not filled, meaning that the method is not paired. So naturally I thought it was being overridden by another method. So here is what the method looks like now:
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
Then I went back to my NIB window, unpaired my Single-Player button and then repaired it to aVerySpecificMethodNameForMyButtonListener. Now this is what it looks like:
Then I ran it again and still nothing when I pressed the button. Then I went back to the code:
(insert frustration here)
At this point I restarted Xcode and the Simulator and looked over everything again and it still didn't work.
This is what I tried next which seems about a billion times more simple:
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
I replaced with my show method with the one above. The only thing different is the last line. I disconnected my method from the Single-Player button in the NIB. Then I ran it and I got excited for a second because I got some output. Here is what I got:
(lldb)
Very helpful output C debugger, as always. I spent the next few hours on SO trying to find out what I'm doing wrong. I've tried countless examples and none of them worked.
Does anybody see what I'm doing wrong here? Is it just Xcode being flakey?
Thanks for reading!! All relevant answers are welcomed!
UPDATE:
I also tried changing the declaration of UIButton to a property in the Header file:
#import <Foundation/Foundation.h>
#import "Viewer.h"
#interface MainScreen : NSObject
{
IBOutlet UIView* view;
Viewer* viewer;
}
#property (weak, nonatomic) IBOutlet UIButton* singlePlayerButton;
- (id)init:(Viewer*) theViewer;
- (void)show;
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender;
#end
Thus here is what I changed in MainScreen.m to comply:
#import "MainScreen.h"
#import "Viewer.h"
#implementation MainScreen
- (id)init:(Viewer*) theViewer
{
self = [super init];
viewer = theViewer;
return self;
}
#synthesize singlePlayerButton;
- (void)show
{
[[NSBundle mainBundle] loadNibNamed:#"MainScreenLayout" owner:self options:nil];
[viewer.view addSubview:view];
[singlePlayerButton addTarget:self action:#selector(aVerySpecificMethodNameForMyButtonListener:) forControlEvents:UIControlEventTouchUpInside];
}
- (IBAction)aVerySpecificMethodNameForMyButtonListener:(id)sender
{
NSLog(#"A button was pressed!");
}
#end
Now there are no variable declarations inside of the MainScreen.m file, I moved them all to the Header. The circles next to singlePlayerButton and view are still filled in but the bubble next to the method is not. I ran the code again and got the same output.
So I'd have put this in a comment because its really just a suggestion however I dont have the rep yet so sorry to get your hopes up, but I believe the touch event's scope is only inside the UIView in which it happens. When the touch up event gets triggered it doesn't get passed to the UIView's handler (MainScreen class) and so the event's listener method does not get called... but that you are able to link it in the interface builder to the button is baffling to me, so i could be way off.
Anyways, where I think you should go next is to make your MainScreen a UIView object and see what that does for event handling of the buttons in the view.
Related
I have two custom NSToolbarItems in the toolbar of the application. Each class has a NSButton within, where I setup the button and then set the toolbar item's view to the button (the stop button item for example):
#implementation RBSStopButtonToolbarItem
#synthesize button = _button;
-(id)initWithItemIdentifier:(NSString *)itemIdentifier
{
self = [super initWithItemIdentifier:itemIdentifier];
if(self)
{
// create button
_button = [[NSButton alloc] init];
// set the frame and bounds to be the same size
//[_button setFrameSize:NSMakeSize(64.0, 64.0)];
//[_button setBoundsSize:NSMakeSize(64.0, 64.0)];
// button will not have a visible border
[_button setBordered:NO];
// set the original and alternate images...names are "opposite"
[_button setImage:[NSImage imageNamed:#"StopButtonAlternateIcon"]];
[_button setAlternateImage:[NSImage imageNamed:#"StopButtonIcon"]];
// image position
[_button setImagePosition:NSImageOnly];
// set button type
[_button setButtonType:NSMomentaryChangeButton];
// button is transparent
[_button setTransparent:YES];
// set the toolbar item view to the button
[self setView:_button];
}
return self;
}
I have an IBOutlet for each custom NSToolbarItem:
// toolbar item for start button
IBOutlet RBSStartButtonToolbarItem *_startButtonToolbarItem;
// toolbar item for stop button
IBOutlet RBSStopButtonToolbarItem *_stopButtonToolbarItem;
Yet I do not see the images in the custom view toolbar items:
The images are .icns type. The example I attempted to following is here:
NSButton in NSToolbar item: click issue
Is there anyone with experience who can offer advice?
I don't know why, but:
[NSToolbarItem initWithCoder:] is calling [NSToolbarItem setImage:] which is then calling [NSButton setImage:] on the button you have set as the toolbar item's view. This wipes out what you have done.
The example that you are referring to DOES NOT subclass NSToolbarItem.
I recommend that you also DO NOT subclass NSToolbarItem, and instead add a regular NSToolbarItem to the toolbar via interface builder and then in awakeFromNib find that toolbar item via its item identifier and set the button as its view.
I have verified that doing it this way works as expected.
I do not follow why your example doesn't work.
But I have worked out the custom NSToolbarItem with my own way without even using NSToolbarDelegate.
My way is assuming you build your toolbar within a nib and not with code(mostly).
What I am doing is creating my own NSView in my nib with whatever I want in it.
Then I drag this NSView into into my NSToolbar in my nib.
xCode will automatically place your NSView inside an NSToolbarItem.
You can then drag this custom NSToolbarItem into the default items and place it with whatever order you want(so you don't even need to place it by code).
The tricky part is to subclass NSToolbarItem and then within the awakeFromNib of this specific NSToolbarItem subclss you set it's view to the NSView underneath it.
You would also need to refer the NSView into an IBOutlet * NSView within that subclass.
Here is the code of the subclass.
The header file:
#import <Cocoa/Cocoa.h>
#interface CustomToolbarItem : NSToolbarItem
{
IBOutlet NSView * customView;
}
#end
The Obj-c file:
#import "CustomToolbarItem.h"
#implementation CustomToolbarItem
-(instancetype)initWithItemIdentifier:(NSString *)itemIdentifier
{
self = [super initWithItemIdentifier:itemIdentifier];
if (self)
{
}
return self;
}
-(void)awakeFromNib
{
[self setView:customView];
}
#end
I have also wrote a blog post about how I did this:
http://pompidev.net/2016/02/24/make-a-custom-nstoolbar-item-in-xcodes-interface-builder/
I'm trying to retrieve the title of a web page and display it in the NSWindows title. This application is document based and I haven't tried anything in a standalone application with an AppDelegate. How would I go forth and retrieve the title and display it in the window?
UPDATE: Here's my code (Note: Doesn't work quite yet)
TitleWindow.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface TitleWindow : NSWindow <NSApplicationDelegate> {
}
#property
(retain, nonatomic) IBOutlet NSWindow *displayTitle;
#end
TitleWindow.m
#import "TitleWindow.h"
#import <WebKit/WebKit.h>
#implementation TitleWindow
- (void)displayTitle:(WebView *)sender didReceiveTitle:(NSString *)title forFrame:(WebFrame *)frame
{
if (frame == [sender mainFrame]){
[[sender window] setTitle:title];
}
}
#end
To get an NSWebViews page title.
You use it's mainFrameTitle
NSString * pageTitleString =[_webView mainFrameTitle];
NSLog(#"%#" , pageTitleString);
Read the Docs
example 2.
WebFrameLoadDelegate_Protocol
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)webFrame{
[_window setTitle:[_webView mainFrameTitle]];
}
UPDATE 2
I have not really built a document based app before. So setting one up to display a web view was fun :-P.
Which leads me to think the problem is two fold.
The first thing I released is just adding the example 2 code to the document.m is not enough.
The webview needs a delegate.
What worked for me and may not be (probably not) the right way to do it was in the Document.xib with the webview selected.
The Connections Inspector at the top shows you the frameLoadDelegate.
I connected this to the file's Owner by dragging it over to the file's Owner in the Place holders pane.
This worked.
And each time I changed the webpage from within the document the title changed along with it.
The second thing to note is I think the real method of setting the documents title is:
[self setDisplayName:#"a title"];
which works when set in:
- (void)windowControllerDidLoadNib:(NSWindowController *)aController
{
[super windowControllerDidLoadNib:aController];
// Add any code here that needs to be executed once the windowController has loaded the document's window.
NSURLRequest* request = [NSURLRequest requestWithURL:_theUrls];
[[_webView mainFrame] loadRequest:request];
[self setDisplayName:#"a title"];
}
And a normal string, But if I change it to:
[self setDisplayName:[_webView mainFrameTitle]];
It does not work as the frame is not loaded yet so there is no title.
So you would think then that [self setDisplayName:[_webView mainFrameTitle]]; would work in the WebFrameLoadDelegate. No it does not. And I am not sure why.
So for now you may want to use the code in example 2 code.
Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.
To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies
I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works