简体   繁体   中英

How do you add a subview to UIWindow and prevent it from disappearing when transitioning using UINavigationController?

I am trying to add a "Floating Button" on top of a scrollview with a lot of subviews. When the button is clicked, UINavigation controller prompts a new viewController to appear. When I dismiss the controller, to go back to the original viewController, I want the button to still be there!

To not deal with constraints conflicts, my solution was to add the UIButton to the UIWindow.

[UIApplication.sharedApplication.keyWindow addSubview:_myButton];

Everything works well. But the problem is when I dismiss the new controller to come back to the controller containing the button:

[self dismissViewControllerAnimated:YES completion:nil];

The button is not immediately "there". Only once the animation finishes (the viewController goes fully down), does the button "re-appear". If I got rid of animation by setting:

[self dismissViewControllerAnimated:NO completion:nil];

Everything works well! The button is IMMEDIATELY there. But I really want to keep this animation.

I was wondering if there was a way to have the button STAY on a viewController, or at the very least appear before the animation fully completes to make it seem like an actual floating button on the screen.

If this is not possible, is there another approach to building floating buttons natively on iOS, without installing any additional pods?

Thanks!

Here is a very simple example.

  • add a scroll view to the main view
  • add content to the scroll view
  • add a button to the main view -- not to the scroll view
  • constrain the button at the top-right of the scroll view
  • add a touch up action for the button to present another view controller

Because we are adding the button as a sibling of the scroll view (not as a subview), it will "float" in front of the scroll view, and the scroll view's contents will scroll behind it.

When we present - and then dismiss - another VC, the "floating button" won't be going anywhere, so it will still be visible when we return to the main VC.

Initial view:

在此处输入图片说明

Scroll view content scrolling "behind" the button:

在此处输入图片说明

Code to see it in action:

ViewController.h

//
//  ViewController.h
//  OCSept2019
//
//  Created by Don Mag on 9/12/19.
//

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
@end

ViewController.c

//
//  ViewController.m
//  OCSept2019
//
//  Created by Don Mag on 9/12/19.
//

#import "ViewController.h"
#import "AnotherViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // instantiate a scroll view
    UIScrollView *scrollView = [UIScrollView new];
    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    scrollView.backgroundColor = [UIColor cyanColor];

    // instantiate a stack view
    UIStackView *stackView = [UIStackView new];
    stackView.translatesAutoresizingMaskIntoConstraints = NO;

    stackView.axis = UILayoutConstraintAxisVertical;
    stackView.alignment = UIStackViewAlignmentFill;
    stackView.distribution = UIStackViewDistributionFill;
    stackView.spacing = 40.0;

    // add 20 labels as arrangeed subview of the stack view
    for (int i = 0; i < 20; i++) {
        UILabel *v = [UILabel new];
        v.backgroundColor = [UIColor yellowColor];
        v.text = [NSString stringWithFormat:@"This is label %ld", (long)i + 1];
        v.textAlignment = NSTextAlignmentCenter;
        [stackView addArrangedSubview:v];
    }

    // add the stack view as a subview of the scroll view
    [scrollView addSubview:stackView];

    // add the scroll view as a subview of self.view
    [self.view addSubview:scrollView];

    [NSLayoutConstraint activateConstraints:
     @[

       // constrain the scroll view to all 4 sides of self.view (safe area), with 20-pts "padding"
       [scrollView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor constant: 20.0],
       [scrollView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant: -20.0],
       [scrollView.leadingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.leadingAnchor constant: 20.0],
       [scrollView.trailingAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.trailingAnchor constant: -20.0],

       // constrain the stack view to all 4 sides of the scroll view, with
       //   8-pts left and right "padding"
       //   40-pts top and bottom "padding"
       [stackView.topAnchor constraintEqualToAnchor:scrollView.topAnchor constant: 40.0],
       [stackView.bottomAnchor constraintEqualToAnchor:scrollView.bottomAnchor constant: -40.0],
       [stackView.leadingAnchor constraintEqualToAnchor:scrollView.leadingAnchor constant: 8.0],
       [stackView.trailingAnchor constraintEqualToAnchor:scrollView.trailingAnchor constant: -8.0],

       // constrain stack view width equal to scroll view width - 16 (to account for 8-pt padding on each side)
       [stackView.widthAnchor constraintEqualToAnchor:scrollView.widthAnchor constant:-16.0],

       ]
     ];

    // instantiate a button with Red background
    UIButton *b = [UIButton new];
    b.translatesAutoresizingMaskIntoConstraints = NO;
    [b setTitle:@"Tap to Present Another VC" forState:UIControlStateNormal];
    b.backgroundColor = [UIColor redColor];
    [b setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];

    // add the button as a subview of self.view
    // this will place it *in front of* the scroll view
    [self.view addSubview:b];

    // constrain it at upper-right corner of the scroll view
    [NSLayoutConstraint activateConstraints:
     @[
       [b.topAnchor constraintEqualToAnchor:scrollView.topAnchor constant:8.0],
       [b.trailingAnchor constraintEqualToAnchor:scrollView.trailingAnchor constant:-8.0],
       ]
     ];

    // add a touch up inside action
    [b addTarget:self action:@selector(btnTap) forControlEvents:UIControlEventTouchUpInside];
}

- (void) btnTap {
    // instantiate another view controller
    AnotherViewController *vc = [AnotherViewController new];
    // present it
    [self presentViewController:vc animated:YES completion:nil];
}

@end

AnotherViewController.h

//
//  AnotherViewController.h
//  OCSept2019
//
//  Created by Don Mag on 9/12/19.
//

#import <UIKit/UIKit.h>

@interface AnotherViewController : UIViewController
@end

AnotherViewController.m

//
//  AnotherViewController.m
//  OCSept2019
//
//  Created by Don Mag on 9/12/19.
//

#import "AnotherViewController.h"

@interface AnotherViewController ()

@end

@implementation AnotherViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // green background
    self.view.backgroundColor = [UIColor greenColor];

    // instantiate a button with Blue background
    UIButton *b = [UIButton new];
    b.translatesAutoresizingMaskIntoConstraints = NO;
    [b setTitle:@"Tap to Dismiss" forState:UIControlStateNormal];
    b.backgroundColor = [UIColor blueColor];
    [b setTitleColor:[UIColor lightGrayColor] forState:UIControlStateHighlighted];

    // add it as a subview of self.view
    [self.view addSubview:b];

    // constrain it centered X and Y
    [NSLayoutConstraint activateConstraints:
     @[
       [b.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
       [b.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor],
       ]
     ];

    // add a touch up inside action
    [b addTarget:self action:@selector(btnTap) forControlEvents:UIControlEventTouchUpInside];
}

- (void) btnTap {
    // dismiss this view controller
    [self dismissViewControllerAnimated:YES completion:nil];
}

@end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM