How can a subview access the methods from its superview? I have a button, and when pressed I would like the button to call a method from its superview, but I do not know how.
suppose your super View class name is
MainView.h
sub View Name
SubView.h
So In sub class you can do
MainView *myMainView = (Mainview *)[self superview];
[myMainView someMethod];
Make sure someMethod is public Method.
Other way you could have reference to all the view is set a tag
For example
myMainView.tag = 100; or self.tag = 100;
In the subview you could do
MainView *myMainView = (Mainview *)[self viewWithTag:100];
[myMainView someMethod];
a weird construct but just call the method:
inside a view you have have self.superview
since self.superview is a UIView*, the compiler will claim it is invalid to call method XYZ on it. Cast it to id or to your class name to use it
e.g.
[(id)self.superview myMethod];
or even
id myValue = [(id)self.superview myMethod:param1];
One method is to use delegates.
#protocol ButtonHandlingDelegate <NSObject>
- (void) subviewButtonWasPressed;
#end
In your Subview add this:
#property (nonatomic, weak) id selectionDelegate;
When subview is created, set delegate to superview.
Define Superview as delegate in .h file
#interface SuperView : UIView <ButtonHandlingDelegate>
in Superview .m file
- (void) subviewButtonWasPressed{
// Do somethign about it
}
All of the answers listed are hacky and bad style. You should be using delegation through the subview's ViewController. What you will want to do is create a protocol for your subview with a void method called something like specialButtonPressedOnView:(SUBVIEWCLASS *)view. Then in the subview's view controller you should make yourself the delegate for this protocol and respond to the delegate method from the VC's context.
Using self.superview is a bad idea because you cannot guarantee what your superview is going to be and generally blindly calling method on objects (ESPECIALLY those cast to id) is a really bad idea.
The other answer that suggested having the superview implement the subview's protocol is also not good style because you're creating a dependency between your views. If you were thinking of going down this path your subview should probably just be a subclass of the superview.
One of the core parts of the MVC design pattern is making sure that your views are reusable and totally independent. They are just structures that you can inject your content into, to which they will respond with pretty UI, and they don't need to know anything about what's being passed to them and don't have to ask other views for help. So either using delegation through the subview's ViewController or subclassing is probably the best direction. Both methods preserve the MVC pattern and are much safer than the other suggestions.
Related
In my program I have started doing all initialization of objects in the init method without setting a frame and then in layoutSubviews I set the frames for these objects to make sure that they are properly set.
Firstly is this proper practice to initialize all objects in the init function without a set frame and then in layoutSubviews set each of their frames. The reason for my concern is that it is called quite often.
So I have a UIView subclass where I call these methods in the layoutSubviews
- (void)layoutSubviews
{
[super layoutSubviews];
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
}
This currently works fine and all the objects are set correctly, but the problem is in my colorPicker class when the user touches the screen I adjust the frame of the colorPicker and by doing so this method gets called from the subview colorPicker and it readjusts a frame that it shouldn't since it has been modified in the subview. The subview causes the superviews layoutSubview to be called and this is not what I need.
My question is, is there a way to stop this behavior from happening or should I not use layoutSubviews to set frames because I was told this is a better way of making views programmatically?
Off the top of my head, there's two ways to fix this. You can either move this code to where the view is initialized, either in init, initWithFrame:, or initWithCoder:, depending on which you're using. It's good practice to make a separate method to initialize everything for your view, and call it from all the init methods to make sure it's always initialized correctly no matter how you instantiate the view.
Alternatively, if you want to keep your code in layoutSubviews, in your #interface add a boolean to flag that the frames were already set
#interface MyView : UIView
#property (nonatomic, assign) BOOL framesAreSet;
#end
Then when you set your frames, check if you already did
- (void)layoutSubviews
{
[super layoutSubviews];
if (!_framesAreSet)
{
[self.filterSwitcherView setFrame:self.viewFrame];
[self.drawingView setFrame:self.viewFrame];
[self.textView setFrame:self.textViewFrame];
[self.colorPicker setFrame:self.colorPickerFrame];
_framesAreSet = YES;
}
}
Your issue is likely that your colorPicker class is handling the touch methods to adjust its own frame. Instead, you should handle the touch methods in colorPicker's superview class, and have that superview class adjust colorPicker's frame in response to the touches.
Also, I would recommend doing all UI initialization in initWithFrame:, not init. The reason is because calling init on UIView ends up calling initWithFrame:.
I've an object of UIImageCustom that extend UIImageView, and in a method this class I want to get instance the ViewController, where my object UICustom is added.
In touchesBegan method of my class UIImageCustom, when I touch in UIImage, I want add other UIImage in my ViewController:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UIImageView* newImageView=[[UIImageView alloc] initWithFrame:CGRectMake(60, 200, 200, 200)];
//Here I need add newImageView in my ViewController
[??? addSubview:newImageView];
}
Someone can help me?
You'll have to add a custom property to your UIImageCustom class that contains a reference to the owner view controller if you want to do what you're saying you want to do. But there's a better way.
What I suggest you should be doing is implementing touchesBegan in the view controller that owns the view, and performing any custom logic there.
There are a bunch of ways you could do this (set a custom property in your UIImageCustom class, just have UIImageCustom add this new image view to its own superview (e.g. [self.superview addSubview:newImageView]). But these approaches seem conceptually flawed. A view shouldn't be adding anything other than to itself (and if you turned off clipping, adding this new image view as a subview of the UIImageCustom, itself, might be another approach, certainly less offensive than the previous two I alluded to).
But I might suggest a couple of other approaches:
If your goal is simply to avoid duplication of your touches code, I might put it in a gesture recognizer subclass, rather than attaching it to some random UIImageView subclass. Gesture recognizers is the right place to put this sort of touch-related code. See the Gesture Recognizers section of the Event Handling Guide for iOS.
Or if you really need to marry this gesture with with a bunch of image views, I might use a custom container view controller to mediate this interaction between gestures and image views. See Creating Custom Container View Controllers in the View Controller Programming Guide for more information.
But you could wrap the touches code and the handling of these two image views all within a view controller, and then use view controller containment to add that to an existing view controller.
We don't know enough about the particular design to suggest one over another, but those are a couple of ideas to pursue. Most likely, the custom gesture recognizer approach should be more than sufficient.
Create a category like this
#interface UIViewController (TOP_MODAL)
-(UIViewController*) topModalController;
#end
#implementation UIViewController (TOP_MODAL)
-(UIViewController*) topModalController{
if (self.presentedViewController == nil){
return self;
}
return self.presentedViewController.topModalController;
}
#end
then
AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController* viewController = [delegate.window.rootViewController topModalController];
...
[viewController.view addSubview:newImageView];
When using Interface Builder, a lot of the time I'd set the Class property to one of my custom classes. Say I have a UIView in the nib and I set the class to my MyView class.
That's all well and good, even when loading the nib programmatically.
But, is there a way to set the Class of a UI control when programmatically loading a nib? The reason being that I want to sometimes use the same nib created in IB, but have it associated with a different Class.
I don't think there is a proper way to achieve that programmatically.
You could create a naked UIView object in IB and give it a tag or assign it to an IBOutlet for its identification so taht you can access it programmatically in viewDidLoad.
In viewDidLoad you access it and fetch its frame and superview and probably background colour, alpha, hidden status etc, so that you can set all those values in IB. Then remove the UIView from its superview and nil the property (if any) or release the object respectively (depending on ARC or not). Then create the UI Element that you need, assign it to the property (if any) add it to the superview of the former UIView, set its frame and properties accordingly and go from there.
(There are ways in Obj-C to change an object's class after its instanciation, but I would not recommend doing that in this case, especially when your subclasses come with additional properties and ivars.)
Normally you create a UIView object on your UIViewController's class XIB file using Interface Builder and then you use the Custom Class tool in the Identity Inspector to associate the UIView with a class (The class is a UIView class containing the code for drawing on the UIView object.). Then you have to use #property and #systhesize in your UIViewController class to hook the UIView (using the connections inspector) to your class. This method is OK but in certain circumstances it has limitations.
You can get around all of this pragmatically.
Create the UIView class that will be used to draw on a UIView object. In this class you create the context reference (CGContextRef) to give the drawing tools a context (where to draw) for such things as strings, lines, circles, etc. i.e.
CGContextRef gg = UIGraphicsGetCurrentContext();
In the UIViewController class, in the .h file you need to import a reference to your UIView class. Let's call it: DrawOnView
#import "DrawOnView.h"
Then in the brackets encompassing the #interface place this line:
UIView * draw; // draw can be changed to any name that suits your needs
Then in the .m part of the class inside the viewDidLoad method you need to insert this code:
// Make the size and location that suits your needs
// You can change this on the go in your code as needed, such as if the
// device orientation is changed.
draw = [DrawOnView alloc] initWithFrame:CGRectCreate(50, 50, 100, 200)];
// You can change the background color of the view, if you like:
[draw setBackGroundColor:[UIColor greenColor]];
// Now add the view to your primary view
[self.view addSubview:draw];
Now, in other parts of our program, you can call methods you have declared in the DrawOnView class and refresh (which calls the drawRect method, the primary entry point in your UIView class) by using this reference:
[(DrawOnView*) draw setNeedsDisplay];
This is very important. Do not use:
[draw setNeedsDisplay]; // This will not work!
Let's say you have other methods defined in DrawOnView and want to call them.
Here's an example method (in the .h file):
-(BOOL) wasHotSpotHit: (CGPoint) p;
The actual method could look like this (in the .m file):
-(BOOL) washHotSpotHit: (CGPont) p
{
if(CGRectContainsPont(hotspot.frame, p))
{
return true;
}
return false;
}
Use code like this:
if([(DrawOnView*) draw testIfSpotHit:p])
{
// Do something for when user touches hot spot.
}
Try using "object_setClass(id object, Class cls)" method of Objective c runtime. GoodLuck:)
I have a .xib file for my viewController. It contains a TableView and a UIView. The UIView is backed by a custom class - CommentsBarView (trying to position a small bar with a comments field underneath my tableView).
So in my Document Outline list I have:
view
tableView
comments bar view
UITextView
UILabel
Custom class for "comments bar view" is CommentsBarView.
I have outlets connected from within CommentsBarView to the textfield and label.
(and from the ViewController to the tableView).
When I load my with controller with:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
I can access the tableView property and change the appearance of the tableVIew, however, from my commentsBarView initWithCoder I can not set the text value on my textView and label:
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self.commentTextView setText:#"Helo, World!"];
[self.characterCountLabel setText:#"200"];
}
return self;
}
It seems as if these properties are not available at initWithCoder time.
If I manually, from my controllers initWithNibName, accesses self.commentsBar.label.text = 200, there is no problem.
Am I experiencing a timing issue where the views are not ready yet or can I not nest a view inside a viewControllers view and have it backed by a custom UIView subclass?
IB is confusing me a bit.
Thanks for any help given.
When loading from a XIB file, the IBOutlets are not ready in the init methods of the objects being unarchived.
You need to override awakeFromNib in your CommentsBarView to have access to the ready and connected IBOutlets.
Once you get used to IB it becomes better. Since Cocoa is a MVC (Model-View-Controller) you should probably not create a UIView subclass and set your UIView to it. You should probably put the UIView back to just a UIView. I generally subclass a UIView if I need to have a customized look. For example; a good time to subclass a UIView is if you want it to have a gradient. Then you can reuse your subclass for any UIView you wish to show the gradient.
In your case you are trying to "control" the UITextView and UILabel. You can instead wire-up outlets of your UITextView and UILabel directly to your UIViewController (File Owner). That is the "controller" of the MVC in this case. Think of your UIView as a container that is simply holding the two controls for this example. Now you can use the viewDidLoad method or some other method of you UIViewController to set the values of you UITextView and UILabel. It is generally the UIVeiwController that interprets the data from the Model in Cocoa and places the data where it needs to be. It is not a rock-solid rule, but a good one.
When I want to access the parent UIView of current UIView I declare object of parent UIView in current UIView and access it by assigning the parent UIView object to current view's object property.
Is there any way to get rid of this and directly call the parent view's methods or properties?
I also tried (parentView *) self.view.superview but didn't help.
It gives error while calling function for this object as
[UIVIew unrecognized selector......
If you're calling this directly from a UIView (and not a UIViewController), it should be self.superview instead of self.view.superview.
# Naveed: This is the common code u can use to any view whether it is parent or child view, Just change button name which u want to press and the view name on which u want to go. For example on back button press u want to go on library view then write this code -
-(IBAction)backButtonPressed:(id) sender
{
llibraryView *libraryview=[[libraryView alloc] initWithNibName:nil bundle:nil];
libraryview.modalTransitionStyle=UIModalTransitionStyleCoverVertical;
[self presentModalViewController:libraryview animated:YES];
[libraryview release];
}
Let me know whether ur problem is solved or not.
What I can think is that you want to call the viewcontroller's method from your view, which you added to the viewcontroller's view.
Now you have two options from here, either you set your view controller your view's delegate and then call your viewcontroller's method by [delegate performSelector:] approach.
The other approach is you access your view's superview, and then get it's controller. You can not do that directly, coz if you could do that it would defeat the entire purpose of MVC.
But, still there is a way out, here you go:-
Get to UIViewController from UIView?
Hope, this helps you.