So I am trying to add a view as a subview onto an exisiting View Controller by loading it from an XIB in such manner -
- (void)showInView:(UIView *)aView animated:(BOOL)animated
{
self.view.frame = aView.frame;
dispatch_async(dispatch_get_main_queue(), ^{
[aView addSubview:self.view];
if (animated) {
[self showAnimation];
}
});
}
I see the button on the parent View Controller and it gets pressed too with the little animation that is there on UIButtons by default. The problem is that the IBAction connected to the UIButton never gets called.
Thanks in advance.
Adding the view as a class variable instead of a local variable did it for me.
Related
I have two view controllers in Xcode project (all view controllers are created in storyboard).
First view controller has two (or more) buttons with certain background images. Second view controller should display full-screen background image of certain button after user touch it (certain button).
Second view controller has a property UIImageView that should be allocated and initialized in the code of second view controller (UIImageView not created in storyboard).
Second view controller is a delegate for first view controller and has a method:
-(void) viewController:(ViewController *) viewController buttonPressed: (UIButton *) button.
Every button has a modal segue to second view controller.
So the sequence of actions of application is next (I realized that by debugging):
User touches any button
Button calls an action method in which delegate method viewController:buttonpressed: is called. UIImageView instance is allocated and initialized in this method with the image returned by button backgroundImageForState:
Than method viewDidLoad of second view controller is called, in which UIImageView instance should be added to super view of second view controller and displayed on screen.
The problem is that despite of allocation of UIImageView instance in delegate method viewController:buttonpressed:, that instance is become nil at the start of method viewDidLoad of second view controller. All actions that been made in viewController:buttonpressed: became unavailing.
The code is below:
First View Controller Code
- (void)viewDidLoad {
[super viewDidLoad];
ViewControllerForImage *temp = (ViewControllerForImage *) [self.storyboard instantiateViewControllerWithIdentifier:#"ViewControlForImage"];
self.delegate = temp;
}
-(IBAction)buttonPressed:(id)sender{
[self.delegate viewController:self buttonPressed:sender];
}
Second View Controller Code
#synthesize myImage;
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:myImage];
}
-(void) viewController:(ViewController *) viewController buttonPressed: (UIButton *) button{
myImage = [[UIImageView alloc] initWithImage:[button backgroundImageForState: UIControlStateNormal]];
[myImage setFrame:CGRectMake(0, 0, 320, 504)];
}
Why don't you just pass the information in a prepareForSegueMethod?
Also in your code why are you sending an instance of viewcontroller back to the second view controller? You are not using it at all.
You can see on the gif below that on the first scroll of UITableView cell's content moves a tiny bit. You can barely see it, margins become 1 pixel wider.I've never encountered this before. It seems like there's some layout issue before the first scroll and it resolves itself after the fact. There's no warning in XCode, these custom cells are pretty straightforward, with no layout code overrides.
I don't know where to start, how do I catch this glitch?
UPDATE. I've implemented an obvious workaround for now:
- (void)scrollTableToFixGlitch {
[self.tableView setContentOffset:CGPointMake(0, 1)];
[self.tableView setContentOffset:CGPointMake(0, -1)];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self scrollTableToFixGlitch];
}
Still looking into the problem. I've tried generic UITableViewCells, nothing changed. Seems like it's the problem with View Controller's root view or tableview layout, and not with the table cells.
UPDATE 2.
I ruled out all the animations out of the question, problem lies somewhere in a different region. The glitch is easy to recreate on a much simplified project. My Tab Bar controller is based on MHCustomTabBarController with custom segues and some other additions. Here's what you do to recreate a glitch. Setup a project where your initial VC is embedded in Navigation Controller. The next controller either MHCustomTabBarController or a subclass is pushed to the navigation stack. First visible VC in tab bar is generic Table View Controller. That's it. Glitch appears only if tab bar controller is pushed in navigation stack.
Here's some code that I think matters from tab bar controller:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.childViewControllers.count < 1) {
[self performSegueWithIdentifier:#"viewController1" sender:[self.buttons objectAtIndex:0]];
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if (![segue isKindOfClass:[MHTabBarSegue class]]) {
[super prepareForSegue:segue sender:sender];
return;
}
self.oldViewController = self.destinationViewController;
//if view controller isn't already contained in the viewControllers-Dictionary
if (![self.viewControllersByIdentifier objectForKey:segue.identifier]) {
[self.viewControllersByIdentifier setObject:segue.destinationViewController forKey:segue.identifier];
}
[self.buttons setValue:#NO forKeyPath:#"selected"];
[sender setSelected:YES];
self.selectedIndex = [self.buttons indexOfObject:sender];
self.destinationIdentifier = segue.identifier;
self.destinationViewController = [self.viewControllersByIdentifier objectForKey:self.destinationIdentifier];
[[NSNotificationCenter defaultCenter] postNotificationName:MHCustomTabBarControllerViewControllerChangedNotification object:nil];
}
And a custom segue code:
#implementation MHTabBarSegue
- (void) perform {
MHCustomTabBarController *tabBarViewController = (MHCustomTabBarController *)self.sourceViewController;
UIViewController *destinationViewController = (UIViewController *) tabBarViewController.destinationViewController;
//remove old viewController
if (tabBarViewController.oldViewController) {
[tabBarViewController.oldViewController willMoveToParentViewController:nil];
[tabBarViewController.oldViewController.view removeFromSuperview];
[tabBarViewController.oldViewController removeFromParentViewController];
}
destinationViewController.view.frame = tabBarViewController.container.bounds;
[tabBarViewController addChildViewController:destinationViewController];
[tabBarViewController.container addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:tabBarViewController];
}
#end
UPDATE 3
During my research I've found that - viewWillAppear is not called the first time when child controller appears. But it's called in all subsequent times.
Maybe the scrollviews contentSize is wider than your scrollView's frame(width specifically in this case) causing scrolling for both directions.
You can either try to decrease the contentSize width to the scrollView's width and
self.scrollView.alwaysBounceHorizontal = NO;
If this doesn't work, the solution would be to disable horizontal scrolling programatically by the help of the UIScrollView delegate
self.scrollView.delegate = self;
[self.scrollView setShowsHorizontalScrollIndicator:NO];
//for the below UIScrollView delegate function to work do the necessary step in the bottom of my answer.
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView.contentOffset.x > 0)
scrollView.contentOffset = CGPointMake(0, scrollView.contentOffset.y);
}
And in your .h file you should change the interface line to below by adding UIScrollViewDelegate
#interface ViewController : UIViewController <UIScrollViewDelegate>
You most probably know this delegate part very well but for others it might be needed:D
Original answer
Ah, I've finally found the origin of this behaviour. I was almost sure this is happening due to some of the preparation methods are not called properly. As I stated in the update 3 I've found that -viewWillAppear method is not called in TableViewController when my TabBarController is pushed to the navigation stacked. I've found a ton of coverage of this matter on SO, it's a very well known issue apparently.
I've added a simple fix just to check if I'm right in my Custom Segue:
//added this line
[destinationViewController viewWillAppear:YES];
[tabBarViewController.container addSubview:destinationViewController.view];
And it works like a charm, no flickering! Now I have to figure out a more suitable place for this call, since explicit calls to methods like this can break a lot of stuff.
Probably the best place is in -navigationController:willShowViewController:animated: method of UINavigationControllerDelegate.
Anyway, problem solved. Hope it will help someone with the same issue.
UPDATE Actually, I was not completely correct on that. -viewWillAppear is called on my tab bar controller when it's pushed to navigation stack. It's not being translated to TableViewController. So there's no need to access NavigationControllerDelegate. Simple fix to a custom segue is enough.
I have a main viewController xib and depending on the buttons the user clicks it could take them to one of 4 viewControllers xib I have seen this code and seem to be having a error.
iOS: Returning to main XIB from secondary XIB
- (IBAction)Button100:(id)sender
{
[self.aView removeFromSuperview];
self.aView = nil;
}
It canot find the aView in the second xib should the #property (strong) UIView *aView; go in the main appdeligte or just in the main .h file of the first xib?
So I understand that You just want to show and hide appropriate views depending on the button clicked.
One way to do it is to put this in Your root view controller:
-(IBAction) buttonXClicked {
[self presentModalViewController:someViewController animated:YES];
}
(I assume here that You already know how to link a button with it's action, if not please read this tutorial: http://rshankar.com/different-ways-to-connect-ibaction-to-uibutton/ )
and then in Your child view:
-(IBAction) backButtonClicked {
[self dismissModalViewControllerAnimated:YES];
}
Another way (though without animations) is in Your root view controller:
-(IBAction) buttonXClicked {
[self.view addSubview:someViewController.view];
}
and in the child view:
-(IBAction) buttonXClicked {
[self.view removeFromSuperview];
}
I also recommend You check this tutorial on using a navigation controller (which should look better):
http://iosmadesimple.blogspot.com/2012/09/navigation-based-project-doing-it-using.html
I am pushing self view to self.navigationcontroller by allocating. I have a tableView on that view so I am changing the content of tableview. But when I am pressing back button (that is automatically created), I am not able to show previous content. Its showing updated content.
Please suggest.
You set code in viewWillAppear method
- (void)viewWillAppear:(BOOL)animated
{
//code set here
}
If you fill the tableView's data in viewWillAppear: or viewDidAppear:, it will reload even if you only press the back button of your top viewController. If you do not want to have your content changed, you are supposed to use initWithNibName: or viewDidLoad: methoads. They are called only at creation time of the view.
Based on the comments on #Kirti's post, You can check if your viewcontroller is being popped by following method, and take some necessary actions for you controller holding table.
-(void) viewWillDisappear:(BOOL)animated
{
if(![self.navigationController.viewControllers containsObject:self])
{
YourControllerWithTable *instance = [self.navigationController.viewControllers objectAtIndex:self.navigationController.viewControllers.count - 1];
instance.loadOldContent = YES;
}
}
In viewWillAppear: of YourControllerWithTable, you can check:
-(void) viewWillAppear:(BOOL)animated
{
if(loadOldContent)
{
//Do your work here
}
}
You don't push UIView instances onto a UINavigationController instance, only instances of UIViewController.
I'm trying to make a form that spans three tabs. You can see in the screenshot below where the tabs will be. When the user taps a tab, the Container View should update and show a particular view controller I have.
Tab 1 = View Controller 1
Tab 2 = View Controller 2
Tab 3 = View Controller 3
The view controller shown above has the class PPAddEntryViewController.m. I created an outlet for the Container view within this class and now have a Container View property:
#property (weak, nonatomic) IBOutlet UIView *container;
I also have my IBActions for my tabs ready:
- (IBAction)tab1:(id)sender {
//...
}
- (IBAction)tab2:(id)sender {
//...
}
- (IBAction)tab3:(id)sender {
//...
}
How do I set the container in those IBActions to change the view controller that the Container View holds?
Among a few other things, here's what I've tried:
UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"vc1"];
_container.view = viewController1;
...but it doesn't work. Thanks in advance.
Switching using Storyboard, Auto-layout or not, a Button of some sort, and a series of Child View Controllers
You want to add the container view to your view and when the buttons that 'switch' child view controllers are pressed fire off the appropriate segue and perform the correct setup work.
In the Storyboard you can only connect one Embed Segue to the Container View. So you create an intermediate handling controller. Make the embed segue and give it an identifier, for example EmbededSegueIdentifier.
In your parent view controller wire up the button or whatever you want and keep are reference to your child view controller in the prepare segue. As soon as the parent view controller loads the segue will be fired.
The Parent View Controller
#property (weak, nonatomic) MyContainerViewController *myContainerViewController;
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"EmbeddedSegueIdentifier"]) {
self.myContainerViewController = segue.destinationViewController;
}
}
It should be fairly easy for you to delegate to your container controller the button presses.
The Container Controller
This next bit of code was partly borrowed from a couple of sources, but the key change is that auto layout is being used as opposed to explicit frames. There is nothing preventing you from simply changing out the lines [self addConstraintsForViewController:] for viewController.view.frame = self.view.bounds. In the Storyboard this Container View Controller doesn't do anything more that segue to the destination child view controllers.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%s", __PRETTY_FUNCTION__);
[self performSegueWithIdentifier:#"FirstViewControllerSegue" sender:nil];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UIViewController *destinationViewController = segue.destinationViewController;
if ([self.childViewControllers count] > 0) {
UIViewController *fromViewController = [self.childViewControllers firstObject];
[self swapFromViewController:fromViewController toViewController:destinationViewController];
} else {
[self initializeChildViewController:destinationViewController];
}
}
- (void)initializeChildViewController:(UIViewController *)viewController
{
[self addChildViewController:viewController];
[self.view addSubview:viewController.view];
[self addConstraintsForViewController:viewController];
[viewController didMoveToParentViewController:self];
}
- (void)swapFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController
{
[fromViewController willMoveToParentViewController:nil];
[self addChildViewController:toViewController];
[self transitionFromViewController:fromViewController toViewController:toViewController duration:0.2f options:UIViewAnimationOptionTransitionCrossDissolve animations:nil completion:^(BOOL finished) {
[self addConstraintsForViewController:toViewController];
[fromViewController removeFromParentViewController];
[toViewController didMoveToParentViewController:self];
}];
}
- (void)addConstraintsForViewController:(UIViewController *)viewController
{
UIView *containerView = self.view;
UIView *childView = viewController.view;
[childView setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addSubview:childView];
NSDictionary *views = NSDictionaryOfVariableBindings(childView);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[childView]|"
options:0
metrics:nil
views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[childView]|"
options:0
metrics:nil
views:views]];
}
#pragma mark - Setters
- (void)setSelectedControl:(ViewControllerSelectionType)selectedControl
{
_selectedControl = selectedControl;
switch (self.selectedControl) {
case kFirstViewController:
[self performSegueWithIdentifier:#"FirstViewControllerSegue" sender:nil];
break;
case kSecondViewController:
[self performSegueWithIdentifier:#"SecondViewControllerSegue" sender:nil];
break;
default:
break;
}
}
The Custom Segues
The last thing you need is a custom segue that does nothing, going to each destination with the appropriate segue identifier that is called from the Container View Controller. If you don't put in an empty perform method the app will crash. Normally you could do some custom transition animation here.
#implementation SHCDummySegue
#interface SHCDummySegue : UIStoryboardSegue
#end
- (void)perform
{
// This space intentionally left blank
}
#end
I recently found the perfect sample code for what I was trying to do. It includes the Storyboard implementation and all the relevant segues and code. It was really helpful.
https://github.com/mhaddl/MHCustomTabBarController
Update: UITabBarController is the recommended way to go, as you found out earlier. In case you'd like to have a custom height, here is a good start: My way of customizing UITabBarController's tabbar - Stackoverflow answer
As of iOS 5+ you have access to customize the appearance via this API; UIAppearance Protocol Reference. Here is a nice tutorial for that: How To Customize Tab Bar Background and Appearance
The most obvious way to achieve what you're looking for is to simply manage 3 different containers (they are simple UIViews) and implement each of them to hold whatever content view you need for each tab (use the hidden property of the containers).
Here is an example of what's possible to achieve with different containers:
These containers "swapping" can be animated of course. About your self-answer, you probably chose the right way to do it.
have a member variable to hold the viewController:
UIViewController *selectedViewController;
now in the IBActions, switch that AND the view. e.g.
- (IBAction)tab1:(id)sender {
UIViewController *viewController1 = [self.storyboard instantiateViewControllerWithIdentifier:#"vc1"];
_container.view = viewController1.view;
selectedViewController = viewController1;
}
to fire view did appear and stuff call removeChildViewController, didMoveToParent, addChildViewController, didMoveToParent
I got this to work by using a UITabBarController. In order to use custom tabs, I had to subclass the TabBarController and add the buttons to the controller in code. I then listen for tap events on the buttons and set the selectedIndex for each tab.
It was pretty straight forward, but it's a lot of junk in my Storyboard for something as simple as 3 tabs.