How can I hide/remove a subview by default when it's superview loaded but still have access to it? - objective-c

I have a view which displays a UILabel and UITextField subviews as well as an UIImageView and 2 UIButtons. These all make up a form. One of the fields is dedicated to date entry in dd/mm//yyyy format.
I decided to create another view (called datePickerView) inside my main view which holds a UIDatePicker instance. When the date field on the form mentioned above is clicked this view with the datepicker is show. Upon clicking the done button the view is hidden/removed again.
I have methods that deal with the showing and hiding of this view:
#property (weak, nonatomic) IBOutlet UIView *datePickerView;
- (void) hidePickerView {
[UIView animateWithDuration:0.5
animations:^{
[[self datePickerView] setFrame:CGRectMake(0, -250, 320, 50)];
} completion:^(BOOL finished) {
[[self datePickerView] removeFromSuperview];
}];
}
- (void) showPickerView {
[[self view] addSubview:[self datePickerView]];
[[self datePickerView] setFrame: CGRectMake(0, -250, 320, 50)];
[UIView animateWithDuration:1.0
animations:^{
[[self datePickerView] setFrame: CGRectMake(0, 152, 320, 260)];
}];
}
I call these methods in my textFieldDidEndEditing and textFieldShouldBeginEditing UITextField delegate methods.
My problem is the the view with the datepicker is visible when it's superview is first loaded. I tried dragging this datepicker view to the top of the hierarchy above all the textfields and buttons but this only shifts the view behind them.
I've also tried hiding the view through storyboard interface, tried adding a hide method to viewDidLoad, viewWillAppear. The methods I tested were:
[self datePickerView] removeFromSuperview];
[[self datePickerView] setHidden:YES];
[self hidePickerView];
These hide the datePickerView no problem but when I click the textfield the datePickerView doesn't show.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
// missing since date field error checking
if (textField == [self missingSinceField]) {
[self showPickerView];
return NO;
}
return YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField {
if (textField == [self missingSinceField]) {
[self hidePickerView];
}
}
When I don't hide or remove the datePickerView in any way shape or form and tap it the datePickerView is loaded no problem.
** Just before posting this question I set a breakpoint in my showDatePicker method and when I uncomment [self datePickerView] removeFromSuperview]; I see that my datePickerView outlet is nil so this may explain why I'm having the issue I'm having. Commenting it again and trying again shows it as not being nil.
I think this is what's wrong but not sure how to make sure when viewDidLoad is run that my datePickerView isn't nil. I can't have it showing in the background of the form under the text fields and I don't think changing the colour of it to white so users can't see it isn't elegant.
Help would be appreciated.
Kind regards

I guess your IBOutlet property for datePickerView is weak, so, when you remove it from the superview it gets destroyed.
Either make it strong, or use the hidden property instead (remembering to set hidden to NO in showPickerView and YES in hidePickerView - where you currently add and remove the view).

You probably want to use the hidden property of the UIView subclass you are working with.
Source:
UIView reference
As far as your outlet being nil - make sure you have connected the IBOutlet to an actual element in the storyboard.

Related

Why is my UIViewController being loaded twice? iOS7

I am presenting my custom UIViewController (called "temp") with a custom animation. The UIVC gets called with:
[temp setModalPresentationStyle:UIModalPresentationCustom];
temp.transitioningDelegate = self;
[temp.view setHidden:YES];
[self presentViewController:temp animated:YES completion:nil];
My custom animation is presenting a view modally from right to top-left position of the screen. It is being presented hidden so the user doesn't see the animation. After it reaches the SCREEN_HEIGHT (768) position it is being set to visible and animated (moved) from top to bottom being presented in the middle. The goal was to present a view from top to bottom and dismiss it from top to bottom (like a movie scene). This code is the NOT working one:
- (void)animateTransition:(id)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
NSLog(#" fromViewController %# ",fromViewController);
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
NSLog(#" toViewController %# ",toViewController);
UIView *containerView = [transitionContext containerView];
if (self.presenting)
{
// set starting rect for animation toViewController.view.frame = [self rectForDismissedState:transitionContext];
[containerView addSubview:toViewController.view];
[UIView animateWithDuration:0.5
animations:^{
toViewController.view.frame = CGRectMake(-self.customSize.width, self.yValue, self.customSize.width, self.customSize.height);
}
completion:^(BOOL finished)
{
//HERE IS THE PROBLEM!!!
[toViewController.view setHidden:NO];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect variable = [self rectForPresentedState:transitionContext];
CGRect fitToCurrentScreenResolution = CGRectMake(0, 0, variable.size.width, variable.size.height);
toViewController.view.frame = fitToCurrentScreenResolution;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
}];
}];
}
else
{
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.frame = [self rectForDismissedState:transitionContext];
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
[fromViewController.view removeFromSuperview];
}
];
}
}
And here is the solution:
- (void)animateTransition:(id)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
NSLog(#" fromViewController %# ",fromViewController);
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
NSLog(#" toViewController %# ",toViewController);
UIView *containerView = [transitionContext containerView];
if (self.presenting)
{
// set starting rect for animation toViewController.view.frame = [self rectForDismissedState:transitionContext];
[containerView addSubview:toViewController.view];
[UIView animateWithDuration:0.5
animations:^{
toViewController.view.frame = CGRectMake(-self.customSize.width, self.yValue, self.customSize.width, self.customSize.height);
}
];
[toViewController.view setHidden:NO];
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
CGRect variable = [self rectForPresentedState:transitionContext];
CGRect fitToCurrentScreenResolution = CGRectMake(0, 0, variable.size.width, variable.size.height);
toViewController.view.frame = fitToCurrentScreenResolution;
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
}];
}
else
{
[UIView animateWithDuration:[self transitionDuration:transitionContext]
animations:^{
fromViewController.view.frame = [self rectForDismissedState:transitionContext];
}
completion:^(BOOL finished)
{
[transitionContext completeTransition:YES];
[fromViewController.view removeFromSuperview];
}
];
}
}
My question is simple. Why is my UIVC being presented twice?
I have tried making my custom UIVC a property which is lazy loaded but my app crashes saying that a UIVC = nil can not be presented modally.
I have tried this solution, but it didn't apply to my problem :viewWillAppear being called twice in iOS5
I also did this with no help: Calling presentModalViewController twice?
I could have used a hack but I wouldn't find out why it is happening. So far it seems that when the animation enters the completion BLOCK it calls the view again.
The apple docs say:
A block object to be executed when the animation sequence ends. This
block has no return value and takes a single Boolean argument that
indicates whether or not the animations actually finished before the
completion handler was called. If the duration of the animation is 0,
this block is performed at the beginning of the next run loop cycle.
This parameter may be NULL.
Is the view being drawn again since the next run loop cycle is being started?
NOTE: Even thought the view is being presented twice, the viewDidLoad method is being called only once.
I would like to know why this is happening. There are some stackoverflow questions with the same code but with different usage scenarios having the same problem without a working solution or explanation.
Thank you for any advice/comment.
iOS 8.0, Xcode 6.0.1, ARC enabled
Yeah you are definetely onto it with "chained animation" (see comment from O.P.).
I witnessed a similar problem trying to hide and show the UIStatusBar for various UIViewControllers in my application, e.g. I have a dummy after load screen UIViewController that shows the same image as the load screen, but it has some added animations.
I am using a custom transition, which features a UIViewController that handles the transition from the "from" UIViewController and the "to" UIViewController by adding or removing their views from itself and assigning the "to" UIViewController "control" to itself. So on and so forth.
In the app. delegate,
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
I had to instantiate the "initial view controller" and then initialize the transition UIViewController with it. Since there are no "to" UIViewControllers the transition UIViewController must hold an initial UIView of the UIViewController it was initialized with until a transition is triggered.
This was done utilizing,
self.window.rootViewController = self.transitionViewController;
[self.window makeKeyAndVisible];
After the very first transition, there always two UIViews overlaid onto each other. And two UIViewControllers one existing as the current control for the transition UIViewController that was assigned during the transition and the previous UIViewController that remains until the transition completes.
This was the code I was trying to use to show/hide the UIStatusBar, one must have the "View controller-based status bar appearance" set to "YES" in the *-Info.plist file.
- (void)viewDidLoad
{
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
}
- (BOOL)prefersStatusBarHidden
{
return false;
}
Whenever the "return" value was changes from default "false" to "true" regardless of when
[self performSelector:#selector(setNeedsStatusBarAppearanceUpdate)];
was triggered, delay, no delay, conditional, etc.; both UIViewControllers, the "to" and "from" were reloaded. At first this was not noticeable, however after implementing an NSURLSession in one of the UIViewControllers that was triggered in the - (void)ViewDidLoad; the problem was clear. The session was executed twice and the graphical content involved was also updated.
I successfully solved the issue in two ways, however I kept the 2nd.
I put everything in -(void)ViewDidLoad; in an if statement and forced it to only be executed once, using an instance variable boolean. The -(void)ViewDidLoad; still loaded twice, however, things that I did not want to execute twice did not.
I transitioned to the UIViewController at which the UIStatusBar hidden state needed to change without using my transitional UIViewController. After the UIStatusBar was shown or hidden, I would reset the "rootViewController" for the app. delegate, once again assigning the transitional UIViewController as always "shown".
I describe how to do this in the following post: https://stackoverflow.com/a/26403108/4018041
Thanks. Hope this helps someone. Please comment on how this could be handled in the future for either the OP or myself.

How do I stop a hidden UIView from making UITextFields unclickable?

I have a UITextfield dedicated to dates. When clicked I have a method that shows a UIView with a UIDatePicker as a subview and a method to hide it again. By default this UIView is hidden.
I've noticed that the textfields of my form are no longer clickable. However my UIButtons are still working.
The UIView is a subview of the superview that the UITextFields and UIButtons are also part of. The UIView also takes up the whole area of the window.
The UIView is an outlet also.
Does anyone have any idea what is happening here?
Kind regard
Code:
- (void) hidePickerView {
[UIView animateWithDuration:0.5
animations:^{
[[self datePickerView] setFrame:CGRectMake(0, -250, 320, 50)];
} completion:^(BOOL finished) {
[[self datePickerView] removeFromSuperview];
}];
}
I've also tried putting the following in my viewDidLoad method:
[[self datePickerView] removeFromSuperview];
I was returning NO in my - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField method. Setting this to yes solved the problem.

Removing View from its superview getting a bad access error XCODE

since the release of the storyboard it has been a long time since I've used addSubview and removeFromSuperview. Obviously I have forgotten how to use them.
So on my UIViewControllerA I have this code for a button:
- (IBAction)buttonClickHandler:(id)sender {
dyf_FacebookViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"Facebook"];
[self.view addSubview:controller.view];
}
Which loads a UIView that is handled by UIViewControllerB. now this view can be called by many different ViewControllers to be a subview, which is why I have it remove itself from the superview.
So on UIViewControllerB I have this code:
- (IBAction)close:(id)sender {
[self.view removeFromSuperview];
}
Unfortunately that returns with a bad access error. Please help!
Thanks
Michael
EDIT:
The subview added (subview's viewcontroller) will be in control for removing itself as it is in control of the button on the subview. So I do not have access to the original controller.view variable as that is in ViewControllerA
You'll have to keep a reference to your dyf_FacebookViewController object, so you can do the following:
- (IBAction)close:(id)sender {
[controller.view removeFromSuperview];
}
What you're doing now is to remove your main view from its superview, which can have serious consequences, giving you a bad access.
if ([controller.view superView]) {
[controller.view removeFromSuperview];
}
Try this
- (IBAction)close:(id)sender {
for (UIView *subView in self.view.subviews) {
[subView removeFromSuperview];
}
}
If you want to remove the view you added, then keep a copy of the view controller...
// in this VC.m
#interface
#property (nonatomic, strong) dyf_FacebookViewController *dyfController;
#end
- (IBAction)buttonClickHandler:(id)sender {
self.dyfController = [self.storyboard instantiateViewControllerWithIdentifier:#"Facebook"];
[self.view addSubview:self.dyfController.view];
}
Later, to close the view...
[self.dyfController.view removeFromSuperview];

removeFromSuperview doesn't work properly

In class
#interface StartScene : UIView
I call an instance of
#interface HelpView : UIView {
GameOverMenu* gorm;
PlayScene* tView;
}
and use addSubview. I also got huge code here
-(void) removemyself {
[tView removeFromSuperview];
[gorm removeFromSuperview];
[self removeFromSuperview];
}
-(void)restartPlay {
[tView removeFromSuperview];
[self playSceneDidLoad];
}
-(void)gameOverDidLoad {
[tView removeFromSuperview];
gorm = [[GameOverMenu alloc]initWithFrame:CGRectMake(0, 0, 320, 520)];
gorm.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"backGround.jpg"]];
[gorm checkScore:Scores];
[self addSubview:gorm];
}
-(void)playSceneDidLoad {
[gorm removeFromSuperview];
tView = [[PlayScene alloc]initWithFrame:CGRectMake(0, 0, 320, 520)];
tView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"backGround.jpg"]];
[self addSubview:tView];
[tView ooneFingerTwoTaps];
}
And two sub classes of HelpView:
#interface PlayScene : HelpView
#interface GameOverMenu : HelpView <UITextFieldDelegate>
In StartScene when I push on a button, an instance of HelpView is created and in init method playSceneDidLoad is called.
Inside the PlayScene there is restart button which calls restartPlay method. When game is lost gameOverDidLoad method is called.
And In both PlayScene and GameOverMenu there are quit button, which calls removemyself method, that are supposed to return player to the main menu.
At first glance it should work fine, but if I press restart button for several times and than try to press Quit, it occurs that the views were not removed from superview, one press on a quit button only now removes them one by one.
And we stop on the HelpView, it didn't remove itself (even if I try to call [super removeFromSuperview]; somewhere.
I need to remove views correctly in time and to get to the main menu (StartScene) when quit is pressed. I don't think that a lot of views covering each other is a good variant. What is the problem?
Well I occurs that the point is that if super class' method is called from the subclass and there is such a command [self removeFromSuperview]; or [(someOtherSubview) removeFromSuperview];, it is subclass that uses self or (someOtherSubview). If our subclass doesn't have the pointed subView, than the command would do nothing. And if there is [self removeFromSubview];, subclass would remove itself.
Actually I solved this problem by using buttons as subView of superclass.

adding subviews to UITableViewCell's content view makes cell unselectable

I want to add some static text to a UITableViewCell in a UITextView.
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setBackgroundColor:[UIColor clearColor]];
[addressField setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[addressField setContentInset:UIEdgeInsetsMake(0, 20, 0, 0)];
[addressField setEditable:NO];
[addressField setScrollEnabled:NO];
// change me later
[addressField setText:#"John Doe\n555 Some Street\nSan Francisco, CA, 00000"];
[cell.contentView addSubview:addressField];
[addressField release];
This works great but I this code makes the cell unselectable probably because the UITextView is covering the entire cell.
How can I work around this so that I can have both the UITextView and selectable cells?
btw, I could make the UITextView size a bit smaller but users would still not be able to select the cell if they touch the UITextView.
I think a slightly better way to do it is to create a tap gesture recognizer on the entire table. (For example in your viewDidLoad)
// gesture recognizer to make the entire cell a touch target
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(changeFocus:)];
[tableView addGestureRecognizer:tap];
[tap release];
Then you create a selector (changeFocus: in this case) to do the actual selecting.
- (void)changeFocus:(UITapGestureRecognizer *)tap
{
if (tap.state == UIGestureRecognizerStateEnded)
{
CGPoint tapLocation = [tap locationInView:self.tableView];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:tapLocation];
[self tableView:self.tableView didSelectRowAtIndexPath:path];
}
}
You can make your changeFocus method more elaborate to prevent selections or give focus to specific subviews of the selected indexPath.
I would adopt the following approach in order to keep interaction enabled with both the UITextView and the UITableViewCell.
Declare your controller class (a UITableViewController I guess ?) as UITexView delegate.
When you declare your UITextView, set the table view controller as it's delegate.
Implement one of the UITextViewDelegate methods (ex : - (void)textViewDidChangeSelection:(UITextView *)textView) in your table view controller .m file.
From within this method you can manipulate the targeted cell either with a custom code or by triggering the tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) delegate method through selectRowAtIndexPath:animated:scrollPosition:.
Your code might then look like :
In the table view controller .h file :
#interface MyTableViewController : UITableViewController <UITextViewDelegate> { ...
...
}
In the table view controller .m file :
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setDelegate:self];
...
Then implement this function for example (or any other suitable UITextViewDelegate function) :
- (void)textViewDidChangeSelection:(UITextView *)textView {
// Determine which text view triggered this method in order to target the right cell
...
// You should have obtained an indexPath here
...
// Call the following function to trigger the row selection table view delegate method
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]
}
Note that there are other alternatives like subclassing UITextView and deal with it's touch methods. I would recommend to use the possibilites offered by its delegate protocol though.
Note also that it might be handy to have your UITextView declared or at least referenced as an instance variable of the table view controller class. This will help you easily keep track of which addressField was hit and get the right indexPath.
[addressField setUserInteractionEnabled:NO];
I hope this helps you a bit:
[self.view insertSubview:TextView aboveSubview:TableView];
Or vice-versa based on your requirements.