IOS Accessing one view from another view - objective-c

Basically I have a view controller having two subviews. I want these views to be connected. A touch event should trigger an event from another view and vice versa. I have thought about two solutions.
1-) Accessing views through their view controllers
2-) Each view has a pointer to another view
I am a newbie on IOS and as far I read from other problems it is mentioned that accessing view controller from a view is not suggested. So, what do you guys suggest me to do?
Edit:
I didn't make much progress on coding but my first view is:
#interface PaintView : UIView
-(id)initWithFrame:(CGRect)frame andController:(ViewController*)ctrl;
and i will control the touch event and access my viewcontroller:
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//[self.controller somethingThatAccessToOtherView]
}
and second view will be very similar to that one.

Why dont you use the delegate pattern!
For the two views communicate with each other ..
Its essentially similar to " Each view has a pointer to another view" but in a much more flexible manner
here is a so thread on it and

If you want the simplest, dirtiest, and non recommended way of proceeding. Tag your two views with some sort of unique tag (use say, -20 and -21, or something. So in the view controller where you create the views you do the following.
[v1 setTag:-20];
[v2 setTag:-21];
Then you can do from in say, v2, the following.
[self.superview viewWithTag:-20];
to get a reference to v1. However, this assumes the same superview, and is not a nice way of doing things unless you are sure that the view heirachy will not change (I'm talking, it's a widget you made that no one else is going to touch and you've documented it well anyway).
A better way would be to use a delegate pattern. The ViewController is the delegate of each of the subviews. On touch, the subviews call some method like
[delegate iwastouched:self];
and the delegate has a method like
-(void) iwastouched:(UIView *) someview {
if(someview == v1){
//view 1 was touched, do something to view two
}
if(someview == v2){
//view 2 was touched, do something to view one
}
}
Another bad way of doing it would be to use notifications. Hell, there are about as many ways to do this as you could like. Some are just not as nice.

Instead of doing this, in the subclass of UIViewController, you should have two members. These member will represent each of the subview.
Now, inside the subclass of UIViewController, add the methods of touch which are of your interest. Inside this methods, identify the view on which any touch event is generated.
Depending on it, pass the event to other view.
You should not have UIViewController as a iVar of your UIView subclass.

Related

UIViewController within a UIViewController

So I have a viewControllerA, and I want to add another View managed by viewControllerB to it. There is only one UISlider activating a simple action in viewControllerB. It won't crash if I don't touch this UISlider, it will once I use UISlider. I am using ARC. I am using:
[self.view addSubView: viewControllerB.view];
to add viewControllerB to viewControllerA. Am I missing something? Thanks.
OK. It looks like a really simple situation. I just added one view controller and one action. Here is the demo project code on github: https://github.com/randomor/Demo
The reason why I want this to work is because I have another app that will create a view controller on the spot and add it to anther view. And I don't want to do it modally, because I don't want the new view controller to cover the whole screen. Thanks.
SOLUTION: So I'm now just using the latest ViewController containment API:
[self addChildViewController:viewControllerB];
It works! as long as I added this line, the event will be passed to its own controller and it stopped crashing.
i recommend you, to use the following code
in ViewControllerA.h
#import "ViewControllerB.h"
in ViewControllerA.m (where you want to push the new controller)
ViewControllerB *newController = [[ViewControllerB alloc]init];
[self presentModalViewController:newController animated:YES];
in ViewControllerB.m you will need
[self.presentingViewController dismissModalViewControllerAnimated:YES];
to make it vanish again.
concerning multiple controllers for one open screen (Apple ViewController Programming Guide):
Each custom view controller object you create is responsible for managing exactly
one screen’s worth of content. The one-to-one correspondence between a view controller
and a screen is a very important consideration in the design of your application.
You should not use multiple custom view controllers to manage different portions
of the same screen. Similarly, you should not use a single custom view controller
object to manage multiple screens worth of content.
You should try and avoid the practice of nesting UIViewControllers. While it is technically supported in iOS5, it is ill-advised, for many reasons, including the type of problem that you're having (you have a dangling pointer to a UIViewController, which is why you are crashing).
http://blog.carbonfive.com/2011/03/09/abusing-uiviewcontrollers/
Although this question is extremely vague, I imagine that you are not keeping a reference to View Controller B, and so when view B tries to interact with it, it causes EXC_BAD_ACCESS.
What's the object that is set as the target for the slider? If it's a EXC_BAD_ADDRESS, then you may not be retaining the target, most probably the view controller for the slider.

Forwarding drag & drop event to parent view

I have an application where I have one custom view which is derived from NSView.
Within this view, there are several custom subviews, which are also derived from NSView.
I want to implement a drag and drop behavior which allows URLs to be dropped onto the views. Everything is already working for the main view.
So, actually I would have to implement dragging behavior handlers on the child-views and the parent-view class.
The thing is, that I don't want to copy the complete handling code to all the child-views to make them also accept drag events. So I thought that it would be the best way to just let them forward all drag events to the parent view.
Is this possible somehow?? Not sure if I can somehow set this up with the responder-chain maybe?
Any tips are highly appreciated!!
Thanks in advance.
I faced a similar issue where I wanted anything dropped in a view, or any of it's subviews to be processed by the view, but the calls never got there.
After some research I found this answer to be the most helpful: https://stackoverflow.com/a/7389711/327471
Essentially if a view is not registered to receive any drag events it should pass that information up to it's parent view automatically. So in my case I ended up with something like this:
NSArray *subviews = [self.view subviews];
for (NSView *aSubview in subviews) {
[aSubview unregisterDraggedTypes];
}
Of course you can be more precise than that, and make sure to only check subclasses of a certain type or whatever parameters you want. But ultimately the key was unregistering the problem subview from it's dragged types.
I hope this helps.
If the subviews are used for display only and don't require any user interaction, you can override -hitTest: in the parent view like so:
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView* hitView = [super hitTest:aPoint];
if(hitView)
return self;
return nil;
}
This makes the parent view receive all mouse events.
Still works XCode 10.1. Swift 4.2. under 10.14.4 Beta (18E184e).
// MARK: - ViewController lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.view.subviews.forEach { $0.unregisterDraggedTypes() }
}
There's probably a better way, but you could put your dragging protocol implementation in a category, rather than in the view directly, and include that category in each of the views.

Storyboard relationship in iOS

I'm attempting to use the new Storyboard feature, however I am confused about Storyboard Relationships? How are they different to IBOutlets?
Also how can I add my own relationship, to my own UIViewController subclass?
I have tried looking in the documentation but can't find much about them.
The way I understand it is: relationships are iOS's way of representing a 'parent-child' relationship and while that does seem quite recursive, an example of parent-child relationship is a UIViewController Container containing a UIViewController.
That's the theory anyway -- it's better understood using the UINavigationController. It is called a 'container' because it contains as many regular UIViewControllers in a 'stack' metaphor so you can do your normal UITableView drill downs and pop offs.
The key point is that the segue between UINavigationController and the first UIViewController in your stack, there is a 'relationship' while the segue between all the rest of the UIViewControllers is just a regular push segue.
The same thing is evident in the UISplitViewController -- it needs two view controllers (sometimes called content view controllers) from launch and these are connected up between the parent UISplitViewController (the container) and two regular (content) view controllers
(thus relationships are not like IBOutlets, but more like segues -- they are even in the 'segues' category of the standard view controller containers)
Now - we aren't allowed to subclass the standard view controller containers, but we are allowed to create custom view controller containers, but I can't for the life of me define a relationship in my custom view controller container!!!!!!!!!!!
so: "can I use them in my own controllers?" the answer is yet unknown (to me at least, and the documentation is thin at best)
Create a subclass of the UIStoryboardSegue like this:
#implementation JTARelationshipSegue
- (void)perform
{
return;
}
#end
Make a custom segue between your two objects and set the class as JTARelationshipSegue. In your view controller make the view controller perform the segue like this:
- (void)viewDidLoad
{
...
[self performSegueWithIdentifier:#"addChild" sender:self];
}
You need to have set the segues identifier in interface builder to addChild.
Impement prepareForSegue:sender so that it adds the segues destination view controller as a child of the current view controller, like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destination = [segue destinationViewController];
[self addChildViewController:destination];
...
}
You will need to have a different segue identifier for each child that you want to create (or another way of identifying the specific view controller.
This will make your storyboard file look prettier, and more readable, however you may do better to just load your other view controllers and add them as children in your view did load method:
- (void)viewDidLoad
{
[self addChildViewController:
[[self storyboard]
initiateViewControllerWithIdentifier:#"myIdent"]];
...
}
I wrote a few tutorials on how to use storyboards over on my site...
Part 1 of tutorial
Part 2 of tutorial
Perhaps that will help a little bit? In essence, the Segue relationships provides an identifier for the link between two items on your storyboard. You can use these identifiers to manage how things work.

View Controller inside View Controller won't receive rotation messages - ios

I have a customized View Controller (we'll call it the wrapper). Its view contains only a UIScrollView. The scroll view contains another customized view controller (we'll call it the inside view), initialized from an xib file (the scroll view itself is initialized from a xib file as well, but I don't believe it matters).
The wrapper view is displayed using a UITabBarController, which contains several more similar view controllers.
I have this weird problem: the wrapper's rotation functions - shouldAutoRotate, willAnimateRotation - get called every time that I rotate the device. For some reason, the inside view's rotation functions don't get called, but it still rotates. The inside view's shouldAutoRotate does get called when initializing it (when the app starts).
I've looked at google and couldn't find anything that is relevant to my case. I'm not sure if it is related, but the Autoresize subviews is checked on all xib files.
I'd be glad if you could help me solve this problem. I need the inside view's rotation function to get called on rotation in order to arrange it manually, but I'd like to avoid calling them from the wrapping view (rather it to work as it should).
Thank you in advance!
Well the innerViewController's rotation function will not be called because they are added as subview's to your scrollView what you can do is generate a NSNotification when orientation changes in your parent controller then you can receieve notification in subview and manage them accordingly. Or you can iterate through subviews of UIScrollView when your shouldAutoRoatate called in you parent controller and then manually call should autorotate method of child views. Hope you understand.
The simplest way is to hold some UIInnerViewController* innerController in your .h file and in .m to call inner's
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation inside of the wrapper like so:
wrapper.m
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
BOOL innerResult = [innerController shouldAutorotateToInterfaceOrientation:interfaceOrientation];
//may be more computations here
return innerResult;// or any other value, based on your needs
}
Other approach that you may use is to register inner controller to UIDeviceOrientationDidChangeNotification like so:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
and layout subviews in -(void)orientationChanged:(NSNotification*)notification;in the same inner controller. The only thing you should be awere of, is that UIDeviceOrientation is a little-bit different than UIInterfaceOrientation and may hold value such UIDeviceOrientationFaceUp that is not applicable to UI changes in most cases.
Nesting viewcontrollers inside custom viewcontrollers is not supported in iOS 4. You can usually forward all the necessary messages to your child VC's manually, though, with acceptable results.

Sending messages from subview to superview in Cocoa, UIResponder?

I have all these subview which are touch sensitive, I want to send a message from the subview to the superview, to say that a user selected it, so the superview can communicate with the rest of the controller.
I can not communicate between the subviews and the controllers,
subviews >> superview >> controller
Perhaps use, UIResponder to achieve this?
Depending on the subviews, the target/action mechanism can be good for this. If you can derive your subviews from UIControl, then it's particularly easy to have your subviews send their action message to their target, which is usually your view controller. You'll be able to lay out your views in Interface Builder and specify their target and action by connecting them to the view controller. If you can't derive them from UIControl for some reason, then you'd have to implement the equivalent of target/action and you won't have the same support in IB, but it's still pretty straightforward.
Another possibility is to let the view controller do the touch handling for all the subviews. This is basically taking advantage of the responder chain as you suggested, but at the touch-handling level. It may not be ideal if there are a lot of subviews to keep track of, but it's workable.
A third way to do it is to have your subviews post a notification when they're selected.
As is, UIResponder doesn't provide a mechanism for passing arbitrary messages along the responder chain. I'm not sure that adding that capability is the most elegant way to send a message specifically from a subview to the view controller. There are potentially many intermediate objects between the view controller and the subviews, and involving the entire chain when you already know where you want the message to go seems wrong. However, it's interesting to think about extending UIResponder to make the responder chain a conduit for more than just events. You could add a category to UIResponder:
#interface UIResponder (Messages)
- (void)sendMessage:(SEL)message withObject:(id)object;
#end;
#implementation UIResponder (Messages)
- (void)sendMessage:(SEL)message withObject:(id)object
{
if ([self respondsToSelector:message]) {
[self performSelector:message withObject:object];
}
else {
[[self nextResponder] sendMessage:message withObject:object];
}
}
#end
WARNING The code above is completely untested, and may be a lousy idea for reasons I haven't yet thought of. Proceed with caution. Expect compilation errors. Cross your fingers. Please let me know if it works out well, and leave me alone if it doesnt.
Why can't you use [self.superview sendMessage]?