I did my research but haven't found an answer to the following problem: I have a custom delegate –subclass of UIView– and for some reason touchesBegan isn't working in the delegate implementation.
TestView.h
#import <UIKit/UIKit.h>
#class TestView;
#protocol TestViewDelegate <NSObject>
#end
#interface TestView : UIView
#property (assign) id <TestViewDelegate> delegate;
#end
TestView.m
#import "TestView.h"
#implementation TestView
#synthesize delegate = _delegate;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"Touch detected on TestViewDelegate");
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#import "TestView.h"
#interface ViewController : UIViewController<TestViewDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UILabel* title = [[UILabel alloc] initWithFrame:CGRectMake(20, 30, 280, 40)];
[title setFont:[UIFont fontWithName:#"Helvetica-Bold" size:30]];
[title setTextColor:[UIColor blackColor]];
[title setTextAlignment:UITextAlignmentCenter];
[title setBackgroundColor:[UIColor clearColor]];
[tile setText:#"Test"];
[self.view addSubview:title];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
#end
What am I missing to make sure touchesBegan from TestView.m gets called when touches occur in ViewController.m?
Your last line indicates a fundamental misunderstanding of views and view controllers. Touches don't occur in view controllers; touches occur in views. After a view is touched, it tells its controller that it was touched, and the controller does something with this information. The way that it does this is through a pattern called delegation.
So let's go through this piece by piece. In order to get what you want, you would have to do the following:
First: create an instance of TestView and add it as a subview of the view controller's view.
Now the view exists, and when you tap it you will see your "Touch detected on TestViewDelegate" logged to the console. But it won't actually do anything with the delegate (there isn't even a delegate yet!).
Second: set the newly created TestView's delegate property to the view controller. Do this after you create the TestView instance but before you add it to the view hierarchy.
Now they're hooked up a little, but the view is never talking to its delegate (this doesn't happen automatically; when you create a delegate protocol you have to specify what messages the view will be able to send it).
Third: add a method to the TestViewDelegate protocol and implement that method in the view controller. This could be something like touchesBeganOnTestView:(TestView *)sender, or whatever else you want the view to tell the delegate when it's touched. That looks like this:
#class TestView;
#protocol TestViewDelegate <NSObject>
- (void)touchesBeganOnTestView:(TestView *)sender;
#end
You have to add the #class line because the protocol declaration comes before the declaration of TestView -- at that point in the file, the compiler doesn't know what "TestView" means, so to avoid a warning you say "don't worry, I'm going to declare this later."
Fourth: invoke that method from TestView's touchesBegan. This is as simple as adding the line [self.delegate touchesBeganOnTestView:self];.
That'll get you what you want. From your question I'm gathering that you're pretty new to iOS/Objective-C, and it's going to be difficult if you don't have a solid understanding of the fundamentals. A good place to start might be Apple's description of delegation.
Related
I have been struggling with this for a few days and have received valuable help on the way from S.O. I have made the simplest possible project to reduce the possibilities of it being a typo.
All my project is, is a ViewController that holds a container view hooked to a childViewController. The "parent" ViewController is set as the delegate of the childViewController. In the viewDidLoad of the child I am passing a value which is just a string. This string should be passed on to the parent and printed on the console. Here are the files.
ViewController.h
#import <UIKit/UIKit.h>
#import "ChildViewController.h"
#interface ViewController : UIViewController <ChildViewControllerDelegate>
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#property NSString *myValueRetrieved;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
ChildViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"ChildVC"];
controller.delegate = self;
NSLog(#"Here is my value: %#",self.myValueRetrieved);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void) passValue:(NSString *)theValue{
self.myValueRetrieved = theValue;
}
#end
ChildViewController.h
#import <UIKit/UIKit.h>
#protocol ChildViewControllerDelegate;
#interface ChildViewController : UIViewController
#property (weak)id <ChildViewControllerDelegate> delegate;
#end
#protocol ChildViewControllerDelegate <NSObject>
- (void) passValue:(NSString*) theValue;
#end
ChildViewController.m
#import "ChildViewController.h"
#interface ChildViewController ()
#property NSArray *colors;
#end
#implementation ChildViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.delegate passValue:#"Hello"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
#end
Am I right to think that when the app is launched, the console should log the following message: "here is my value: hello". Am I doing something wrong in terms of logically not getting delegation or is it just a silly typo somewhere? tx
You're assuming that the view is loaded when the view controller is instantiated. That's now how it works. The view gets loaded when it's needed (like to add to the parent view).
But you can force the view to load and make this work. Call -loadViewIfNeeded on the child view controller right after setting the delegate. That will probably get you what you want:
controller.delegate = self;
[controller loadViewIfNeeded];
NSLog(#"Here is my value: %#",self.myValueRetrieved);
Or, if you do want to call back the delegate in viewDidLoad, then you'd need to move the NSLog to the -passValue: method, since the primary view controller's viewDidLoad method will have already finished running.
To do this make ParentController a delegate of ChildController. This allows ChildController to send a message back to ParentController enabling us to send data back.
For ParentController to be delegate of ChildController it must conform to ChildController's protocol which we have to specify. This tells ParentController which methods it must implement.
In ChildController.h, below the #import, but above #interface you specify the protocol.
#class ChildController;
#protocol ViewControllerBDelegate <NSObject>
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item;
#end
next still in the ChildController.h you need to setup a delegate property and synthesize in ChildController.h
#property (nonatomic, weak) id <ChildControllerDelegate> delegate;
In ChildController we call a message on the delegate when we pop the view controller.
NSString *itemToPassBack = #"Pass this value back to ParentController";
[self.delegate addItemViewController:self didFinishEnteringItem:itemToPassBack];
That's it for ChildController. Now in ParentController.h, tell ParentViewController to import Child and conform to its protocol.
import "ChildController.h"
#interface ParentController : UIViewController
In ParentController.m implement the following method from our protocol
- (void)addItemViewController:(ChildController *)controller didFinishEnteringItem:(NSString *)item
{
NSLog(#"This was returned from ChildController %#",item);
}
The last thing we need to do is tell ChildController that ParentController is its delegate before we push ChildController on to nav stack.
ChildController *ChildController = [[ChildController alloc] initWithNib:#"ChildController" bundle:nil];
ChildController.delegate = self
[[self navigationController] pushViewController:ChildController animated:YES];
I'm showing an NSPopover in an NSView, originating from a point on an NSBezierPath. I'm able to show the popover without a problem, but I can't seem to set the string value of the two text fields in it. The popover and the content view are both a custom subclass of NSPopover and NSViewController, respectively. The NSPopover subclass is also the NSPopover's delegate, although I don't implement any delegate methods, so I'm not sure I even need to do that.
Here is my subclass of NSViewController:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverViewController : NSViewController
#end
#import "WeightPopoverViewController.h"
#interface WeightPopoverViewController ()
#end
#implementation WeightPopoverViewController
- (id)init {
self = [super initWithNibName:#"WeightPopoverViewController" bundle:nil];
if (self) {
}
return self;
}
#end
And my subclass of NSPopover:
#import <Cocoa/Cocoa.h>
#interface WeightPopoverController : NSPopover <NSPopoverDelegate> {
NSTextField *dateLabel;
NSTextField *weightLabel;
}
#property (strong) IBOutlet NSTextField *dateLabel;
#property (strong) IBOutlet NSTextField *weightLabel;
#end
#import "WeightPopoverController.h"
#implementation WeightPopoverController
#synthesize weightLabel;
#synthesize dateLabel;
#end
This is the code in my NSView subclass that opens up the popover:
#interface WeightGraphViewController () {
WeightPopoverController *popover;
WeightPopoverViewController *vc;
}
...
-(void)mouseEntered:(NSEvent *)theEvent {
// initialize the popover and its view controller
vc = [[WeightPopoverViewController alloc] init];
popover = [[WeightPopoverController alloc] init];
// configure popover
[popover setContentViewController:vc];
[popover setDelegate:popover];
[popover setAnimates:NO];
// set labels
for (id key in (id)[theEvent userData]) {
[popover.weightLabel setStringValue:[(NSDictionary*)[theEvent userData] objectForKey:key]];
[popover.dateLabel setStringValue:key];
}
// set the location
(redacted, irrelevant)
// show popover
[popover showRelativeToRect:rect ofView:[self window].contentView preferredEdge:NSMaxYEdge];
}
-(void)mouseExited:(NSEvent *)theEvent {
[popover close];
popover = nil;
}
In WeightPopoverViewController.xib, I've set the File's Owner to WeightPopoverViewController and connected the view to the custom NSView. In this xib I also have an Object set to WeightPopoverController with the dateLabel and weightLabel connected to their text fields and the contentViewController set to File's Owner.
I think where I am going wrong is likely related to how I have configured my class / instance variables for the NSPopover, but from the research I've done and documentation I've read I can't seem to crack where I've gone wrong. Any help would be appreciated.
UPDATE:
I removed the NSPopover subclass from code and from IB. I put my outlets in my NSViewController and connected them in IB. However, I'm still not able to set the string values. The following won't compile with the error "Property 'weightLabel' not found on object of type NSPopover*'".
#interface WeightGraphViewController () {
NSPopover *popover;
...
}
-(void)mouseEntered:(NSEvent *)theEvent {
vc = [[WeightPopoverViewController alloc] init];
popover = [[NSPopover alloc] init];
[popover setContentViewController:vc];
[popover.dateLabel setStringValue:#"test"];
}
I have the property definition exactly as I had it in my NSPopover subclass, but now in my NSViewController. This is actually what I had before, and since I wasn't able to set the properties from the NSViewController, I figured I needed to do it through a subclass of NSPopover. This is why I thought I am having an issue with how I have configured my class / instance variables.
You seem to be creating two popovers, one in code (popover = [[WeightPopoverController alloc] init]) and one in Interface Builder (In this xib I also have an Object set to WeightPopoverController). Have a think about what you’re trying to achieve.
I would also advise against subclassing NSPopover. I believe this is causing confusion and is unnecessary. Instead, put the outlets to your dateLabel and weightLabel in the popover’s content view controller.
I've experienced something that I think is similar. The root problem is that the "outlets" connecting your view (XIB) to your controller are not initialized until after the view has been displayed. If the controller tries to set properties on any UI controls in the view before the popover has been opened, those changes are ignored (since all the controls will be nil).
Luckily, there's an easy solution (as mentioned in this answer): just invoke the view getter on your controller, and it will force the view to initialize sooner.
In other words:
popover = [NSPopover new];
myController = [[MyViewController alloc] initWithNibName:#"MyView" bundle:nil];
popover.contentViewController = myController;
[myController view]; // force view to initialize
...set some values on myController... // works because view is now loaded
[popover showRelativeToRect: ...];
I have a UIViewController that is a delegate of a UIScrollView called NumberLineScroll. I have set the delegate of the scrollview to be the view controller, but I am not getting any calls to scrollViewDidScroll: whenever the scrollview is scrolled. Here's the implementation:
The UIViewController header file (Game) :
#interface Game : UIViewController <LocDelegate, UIScrollViewDelegate, ResultsDelegate> {
...
NumberLineScroll* nline;
...
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView;
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
and the component of the main file where the methods are implemented:
- (void)viewDidLoad {
...
nline = [[NumberLineScroll alloc] initWithFrame: CGRectMake(0, -50, 1024, 400)];
[nline setScrollEnabled:YES];
[nline setContentSize:CGSizeMake(4100, 400)];
[self.view addSubview:nline];
nline.delegate = self;
nline.locDelegate = self; //this is for a Ball imageview that is implemented in NumberLineScroll
...
}
and here's the implementation of the UIScrollView (NumberLineScroll):
header file:
#interface NumberLineScroll : UIScrollView {
NumberLine* nline;
Ball* ball;
id <UIScrollViewDelegate> delegate;
id <LocDelegate> locDelegate;
}
#property(nonatomic, assign)id <UIScrollViewDelegate> delegate;
#property(nonatomic, assign)id <LocDelegate> locDelegate;
There's nothing that's very relevant in the main file. The only programming in there that I do to affect the scrolling is turn the bounce off and to switch the scrolling off when the ball is being moved by the user, then on again when they finish. Also, the locDelegate isn't a problem since that works just fine. The main problem is that NumberLineScroll is making no delegate calls. Why is this?
Also, the NumberLine view in NumberLineScroll is the contentview for the scrollview, just for clarification.
My best guess here is that you're redefining the delegate property inside you NumberLineScroll class. Try deleting the #property(nonatomic, assign)id <UIScrollViewDelegate> delegate; line (and if you have a #synthesize statement in your *.m file delete that one too). Also, delete the following variable declaration: id <UIScrollViewDelegate> delegate;.
Finally, just as a side comment, when you conform to a protocol (like UIScrollViewDelegate) you don't declare the methods that you implement in your *.h file. So, the following lines in your Game class are not needed:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;
- (UIView*)viewForZoomingInScrollView:(UIScrollView*)scrollView;
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;
Hope this helps!
I'm using the Cordova cleaver to insert some subview into parts of my native app. I'm having difficulty retaining the contents of these subviews between the pages of my app. For example if I go from ViewController1 to ViewController2 and then back again the contents of the subview on the first view controller has reset as if it had just been loaded for the first time. I'd like a way to preserve these subviews across the app so they don't reset as a user moves around.
Here's what I'm doing right now:
Retaining the subview as a property in ViewController.h
#import <UIKit/UIKit.h>
#import <Cordova/CDVViewController.h>
#interface ViewController : UIViewController
#property (nonatomic,retain) CDVViewController* viewController;
#end
And then loading it here like so in ViewController.m
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize viewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
viewController = [CDVViewController new];
viewController.useSplashScreen = NO;
viewController.view.frame = CGRectMake(0, 44, 320, 450);
[self.view addSubview:viewController.view];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Any help or pointing in the right direction is greatly appreciated.
There are a few approaches you could take here. One of them is to use a singleton pattern for your view controller, so that only one instance is ever created. With that pattern, the view controller will retain its state because it will never be re-created. An example of that pattern for objective c is here.
But that may not be the best approach. Another option is to store the parts of your view controller that you want to keep the same as static variables, so that if a new instance of the view controller is created, the portions of your view controller that you want to preserve will still be the same. If you do that, you can restore the state of your view controller in a viewDidAppear method.
If you were to use the second approach, I would do it like this. First, remove the #property declaration from your header file for the CDVViewController. Then, in your implementation file do something like this:
#import "ViewController.h"
#import <Cordova/CDVViewController.h>
#interface ViewController ()
#end
#implementation ViewController
static CDVViewController *__MY_STATIC_CDVViewController;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if( ! __MY_STATIC_CDVViewController ) {
__MY_STATIC_CDVViewController = [CDVViewController new];
__MY_STATIC_CDVViewController.useSplashScreen = NO;
__MY_STATIC_CDVViewController.view.frame = CGRectMake(0, 44, 320, 450);
}
[self.view addSubview: __MY_STATIC_CDVViewController.view];
}
#end
I would add that this recommendation isn't the best overall approach for your problem, but it should work for your needs.
I have an NSImageView subclass that I use for dragging and dropping Files onto in my app.
To do this I created a subclass of NSImageView and also dragged the NSImageView onto my XIB, I then set the class of the XIB NSImageView Object to my custom subclass.
Everything works well with the drag and drop and I know I have the correct file after the drag.
The problem comes in when I want to update a textfield on the MainViewController based on the file dragged in.
I created the following subclass and protocol
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#protocol PDFDraggedIntoWell <NSObject>
#required
-(void)PDFDraggedIntoWellWithURL:(NSURL*) importedURL;
#end
#interface DragAndDropImageView : NSImageView
#property (strong) id <PDFDraggedIntoWell> delegate;
#end
Then in my implementation in the subclass I try to call the delegate method
-(void) finishedDragginInFileWithURL:(NSURL*) importedURL{
if( self.delegate != nil && [ self.delegate respondsToSelector: #selector(PDFDraggedIntoWellWithURL:)]) {
[self.delegate performSelector:#selector(PDFDraggedIntoWellWithURL:) withObject:importedURL];
}
}
The problem I run into is how do you assign the delegate. From the XIB NSImageView to my MainviewController I connect up an IBOutlet
#property (weak) IBOutlet DragAndDropImageView *draggedFileImageView;
And I have declared that my ViewController will receive the delegate
#interface MyMainUIViewController ()<PDFDraggedIntoWell>
with the appropriate method implemented
-(void) PDFDraggedIntoWellWithURL:(NSURL *)importedURL{ }
Now I have tried in various places to assign delegate to self (in viewDidLoad - which doesn’t get called since the view is being loaded in a XIB??) and also in
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
but all I get back is the delegate is still nil when debugging.
What am I doing wrong? Thanks for the help!!
If you are using xibx the initWithCoder method is called for initialization. Set your delegate there.
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, 10)];
myView.delegate = self;
}
return self;
}
Alternatively set the delegate via interface builder by dragging wile holding ctrl from the File's Owner to your view. Like this:
I was able to add add the delegate to the awakefromnib(in mymainviewcontroller) method and things are working fine now. Thanks