Overriding drawRect method in a UIView subclass & call it into another UIViewcontroller - ios

I am a novice ios programmer and this is my first project.I am going to develop this project specifically for ipad. In this project i need to draw several circles & display data on it by parsing xml element.I have done the the circle drawing part by subclassing UIView class and overriding drawRect method. I load the UIView subclass in a UIViewcontroller via loadview method .Now what i need to do is
Touch a circle and switch to another UIViewcontroller.
I am not sure how to switch to another UIViewcontroller because all of drawing and touch detecting code is in my UIView subclass.
A help will be appreciated

You need to use a delegate method to tell the parent view controller that there was a touch, so it can present another view controller.
At the top of your UIView subclass header, add this:
#protocol MyCustomViewDelegate <NSObject>
- (void)customViewCircleTapped;
#end
Then, in your declaration of the view (the existing declaration you have of your custom view subclass):
#interface MyCustomView : UIView
...
#property (weak) id<MyCustomViewDelegate> delegate;
After that, in your view controller, you need to set view.delegate = self, so the view can reference the view controller.
Then, in your view controller header, change your declaration to look like this:
#interface MyViewController : UIViewController <MyCustomViewDelegate>
then implement customViewCircleTapped in the view controller implementation:
- (void)customViewCircleTapped {
... // Open a view controller or something
}
Once you have done that, in the touch detection code in your view, you can add:
[self.delegate customViewCircleTapped];
What this does is gives your custom view the ability to tell its parent view controller that something has happened, by calling this method (you can change it and add arguments if you need to pass data), and then the view controller can open another view controller or perform some action based on this.
View detects touches → Process touches → call customViewCircleTapped delegate method on view controller → view controller opens another view controller

Provide the subclassed view with a delegate notifying the view is touched and in the delegate call in the mainVC do the push job

The way I like to do this is to send a message up the responder chain. This completely decouples the view from it's enclosing views and view controllers. So, when your circle view is tapped, it emits a "circle view was tapped message" to the responder chain--the first object on the responder chain that responds to that message (which don't know/care which one) will have it invoked. It's simple to implement.
You attach a UITapGestureRecognizer to your view with -tapped: as the action.
On your view, your tap action might look like this.
-(IBAction)tapped:(UIGestureRecognizer*)g
{
[ self sendAction:#selector( circleViewTapped: ) withObject:self ] ;
}
The -sendAction: method on UIResponder is added via a category, like this:
#implementation UIResponder (ActionSending)
-(void)sendAction:(SEL)action withObect:(id)object
{
UIResponder * target = self ;
while( self && ![ target respondsToSelector:action ] )
{
target = [ target nextResponder ] ;
}
[ target performSelector:action withObject:object ] ;
}
#end
Your view controller or any parent view or parent view controller that responds to circleViewTapped: will have that method invoked when your circle view is tapped.
A note about organizing your views:
I would make a circle view UIView subclass. Instantiate your subclass for each circle to be displayed. To each of those attach a UITapGestureRecognizer. The target of your gesture recognizer is the -tapped: method, above.

The current answers are overcomplicating the solution. You don't need gesture recognisers or delegate protocols.
Make your circle drawing view a subclass of UIControl instead of UIView. Then attach your view controller as a target to the UIControlEventTouchUpInside event:
[circleView addTarget:self action:#selector(circleTapped:) forControlEvents:UIControlEventTouchUpInside];
This will call the circleTapped: method on your view controller, with the tapped view as the sender.

Related

How to segue back from one view controller to another within a custom UIView?

I want to segue back from ViewControllerTwo to ViewControllerOne. I created a button that is responsible for doing that, but my problem is that the button is part of custom UIView class that is added to ViewControllerTwo, the button is not a part of the main view of ViewControllerTwo.
So in the custom UIView class I have the method that reacts if the button is clicked...
-(void)buttonClicked{
[SecondViewController performSegueWithIdentifier: "ShowFirstViewController" sender:nil];
}
When I do this I get an error: "performSegueWithIdentifier not a method of class" which makes sense.
So how can I segue between two viewcontrollers where the button responsible for the segue is not actually part of either view controller and is in a different class.
I think you can have a delegate call back to your SecondViewController and implement the performSegueWithIdentifier in the delegate callback method in SecondViewController.
It goes like this:
Above your custom UIView class interface create a protocol like this
#protocol CustomViewDelegate <NSObject>
- (void)buttonDidTap;
#end
Then create a property in your interface
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
In your custom UIView *.m add this
-(void)buttonClicked{
[self.delegate buttonDidTap];
}
Conform the protocol to your SecondViewController like this
#interface SecondViewController: UIViewController <CustomViewDelegate>
set the delegate in your viewDidLoadMethod like this
-(void)viewDidLoad{
[super viewDidLoad];
self.yourCustomView.delegate = self;
}
implement this method inside the view controller .m file
- (void)buttonDidTap{
[self.performSegueWithIdentifier: "ShowFirstViewController" sender:self];
}
I'm more of a swift guy i think this should work fine.
iOS 9.3, Xcode 7.3, ARC enabled
This is what I'd do to troubleshoot:
Step 1: Make sure that you have a proper storyboard identifier for the view controllers you wish to segue between. The views simply attach to the view controllers, custom or not.
To do this, go to "*.storyboard" show the Utilities (right pane) and navigate to the Identity Inspector. Make sure you have "ShowFirstViewController" entered in the Storyboard ID field.

Swift: Access Parent ViewController Segue from UIScrollView

What I have so far
I have a view controller ViewController1 which has 2 segues, one that goes to ViewController2 and one that comes back.
The segue from vc2 - vc1 is called "ViewC2toViewC1Segue"
On ViewController2 I have a UIScrollView that loads in two new viewcontrollers and allows me to scroll left or right to view them.
All these work fine, the data shows ok and everything displays nicely. However, On one of these subviews I want to be able to display an option to go back to ViewController1.
in my naivety I tried just using:
performSegueWithIdentifier("ViewC2toViewC1Segue", sender: self)
I hope this image helps explain:
The Problem
These two viewcontrollers that are loaded in the UIScrollView or not on the main storyboard so I can not CTRL and DRAG.
Question
How do I access the segue (ViewC2toViewC1Segue) of the view controller (ViewController2) that is holding the UIScrollView from one of UIScrollViews Child view containers.
I hope that makes sense.
The best way to do this is with a delegate protocol. Your parent view controller would be the delegate of the child. When the button is pushed on the child, it messages its delegate (the parent, who has the scroll view), and the parent handles scrolling to the other view controller.
In your ChildViewController file, you want to do 3 things:
Define the delegate protocol. This is a set of functions that the delegate object needs to implement.
Add a delegate property to the ChildVC class. This allows the ChildVC to call functions on the delegate.
Call the delegate function when the button is pressed
The protocol declaration would look something like this
protocol ChildViewControllerDelegate: class {
func childViewControllerDidSelectBack(childViewController: ChildViewController)
}
The delegate variable declaration would look like:
class ChildViewController: UIViewController {
weak var delegate: ChildViewControllerDelegate?
}
To call the delegate function, in your button handler code simply write:
delegate.childViewControllerDidSelectBack(self)
In your ParentViewController file, you want to do 3 things:
Set yourself as the delegate for the ChildVC
Declare that you conform to the ChildVCDelegate protocol
Implement the delegate protocol methods
To set yourself as the delegate, whenever you instantiate the child VC, do something like:
childVC.delegate = self
To declare that you conform to the protocol, make your class definition look like:
class ParentViewController: UIViewController, ChildViewControllerDelegate
Lastly, you need to implement the protocol function
func childViewControllerDidSelectBack(childViewController: ChildViewController){
// code to scroll the scrollview
}
}
Hopefully this helps!
You can use notification approach here.
On button click of one of your ViewController (in scrollview) post
notification.
Add observer in ViewController2's viewDidLoad method.
Go back to ViewController1 in selector method.
===================================================
//ViewController2
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(navigateToViewController1) name:#"navigateToViewController1" object:nil];
// Do any additional setup after loading the view from its nib.
}
-(void)navigateToViewController1 {
//[self.navigationController popViewControllerAnimated:YES];
OR
// perform segue
}
//In button click event of your viewcontroller inside scrollview.
[[NSNotificationCenter defaultCenter] postNotificationName:#"navigateToViewController1" object:nil];

Access contained UIView component from Parent ViewController

In my parent viewcontroller, I have a view container, I added a subview to it:
m_cardDetail = [[CardDetailView alloc] init];
[_m_viewContainer addSubview:m_cardDetail];
[m_cardDetail initialize];
But how can I let my parent viewcontroller process a button click (button resides in m_cardDetail subview). I tried setting the button tag property of the button to 1010 and using this code in parent viewcontroller:
UIButton *aButtonView = (UIButton *)[m_cardDetail viewWithTag:1010];
[aButtonView addTarget:self action:#selector(aButtonTapped:) forControlEvent:UIControlEventTouchUpInside];
and also added this in viewcontroller:
- (void) aButtonTapped:(UIButton *) sender {
//Do something
}
but im getting the Unrecognized selector error
You should treat another view controller's views as private, even one that is a child view controller. As #Ostanik suggests in his answer (voted), setting up a protocol and a delegate is a good way to make a connection between a parent and child.
Note that an embed segue is a very clean way to set up the parent/child link. You simply create a container view in IB and control-drag from the container view to the scene of the view controller that you want to be a child, and Xcode does the rest. When the parent view controller is loaded the embed segue is invoked and you can set up the delegate in your prepareForSegue method.

setting Delegates on destination viewController

This is puzzling me.
The context
The original tutorial I'm following.
Where the segue is added to the Main View via a custom segue:
- (void) perform {
MainViewController *source = (MainViewController *)self.sourceViewController;
UIViewController *destination = (UIViewController *) self.destinationViewController;
for(UIView *view in source.main.subviews){
[view removeFromSuperview];
}
source.currentViewController = destination;
destination.view.frame = CGRectMake(0, 0, source.main.frame.size.width, source.main.frame.size.height);
[source.main addSubview:destination.view];
}
The TextField is connected as delegate in the child View Controller. All things being equal I get the app crashed without any message.
The workaround
In the Main View Controller, in -(void)prepareForSegue: I've added [segue.destinationViewController setDelegate:self]; in the meantime I've added a property in the child View Controller id<UITextFieldDelegate> delegate and modified the textfield delegate as self.delegate.
This works, but the trouble is that I've to set the delegated methods in Main View Controller which is not quite efficient as I have more View Controllers to add.
The Objective
How do I set each View Controller to be the delegate for itself without crashing?
The immediate cause of your error is that the view controller that your views belong to is being deallocated. The fact that your views are on screen while their view controller is deallocated highlights a fundamental flaw in the approach of taking views off one view controller and adding them to another. View controller containment is the correct way to solve an issue like this.
Changing the currentViewController property to strong will fix the memory management issue you're seeing, but it's just a bandaid. Your currentViewController will still be missing rotation methods, appearance and disappearance methods, layout methods, and so forth. View controller containment ensures these methods get called for the view controller whose views are on screen.
Here is an altered version of your project that illustrates how to use view controller containment. I think that will be a better solution than manually removing and adding subviews of the view controllers themselves. See the Apple docs for more info on custom view controller containers.
At first, let's see crash report. Please, do the following:
1. Add Exception Breakpoint
2. Edit it as in the picture
You should create a custom class for the destinationViewController wich will implement UITextFieldDelegate
#interface DestinationViewController <UITextFieldDelegate>
#end
And from storyboard add the class to UIViewController that has TextField
And make the connections for elements and TextField delegate.
Implement delegate methods.
You will not need the implementation of prepareForSegue: anymore. You will have two different classes with different elements. Only if you need to pass something from source to destination then you use prepareForSegue:
Hope you'll understand

Update IBOutlet on Main View from Sub View

I have a main view which has a UISlider on it.
From the main view I add a subview using:
gameView *myViewController = [[gameView alloc] initWithNibName:#"gameView" bundle:nil];
[self.view addSubview:myViewController.view];
The subview is created on top of the main view.
When I remove the sub view using:
[self.view removeFromSuperview];
the main view underneath becomes visible.
I want to be able to update the value of the UISlider on the main view, from the sub view, before I call [self.view removeFromSuperview]
Is it possible?
Basically the question can be generalized to how to update an IBOutlet on the main view from the sub view.
Help is greatly appreciated.
Many thanks!
Yes, it's possible.
And there's a few ways to do this. Here's how I would do it:
First, make your parent view controller's UISlider a property that can be accessed by other objects.
Secondly, give your gameView object an instance variable that you'll link to the parent view (let's call it id savedParent;)
Then, before you do removeFromSuperview, you can simply do something like:
ParentViewController * parentVC = (ParentViewController *) savedParent;
if(parentVC)
{
// some float value of whatever you want to set the slider value to
parentVC.slider.value = 0.5f;
}
Also, why are you instantiating a whole View Controller object (gameView) if you simply want to add a subview? When you do your removeFromSubview call, the view gets removed but your gameView view controller isn't released (and might even be getting lost & leaked in memory, leading to a crash). If you want to do a subview, subclass UIView. If you want to push a new view controller, push the whole controller (and not just the view it contains).
Here is another way:
I'm not sure what the slider is representing, but you need to create an object that represents this
#interface MyGameThing : NSObject
#property (assign) CGFloat myValue;
#end
#implementation MyGameThing {
CGFloat *_value;
}
#synthesize myValue = _myValue;
#end
You then need to pass that object to both of your view controllers (or make it a singleton).
Then, on ParentViewController, in the viewWillAppear, just set the slider to the new value.
Daniel.
(p.s. don't just add view controllers views to the superview, use presentModalViewController / dismissModalViewController or a navigation controller).

Resources