How would I create a status message that briefly a

2019-05-14 16:14发布

问题:

I'm trying supplement a UIRefreshControl with a UIView containing a message (statusMessageView) that appears "above" a UITableViewController after the app launches. This view will inform the user that the UITableViewController is already refreshed and does not need refreshing.

Here is a breakdown of what I am trying to accomplish:

The app is launched, the UITableViewController appears normal then scrolls down 44px to reveal statusMessageView (with a height of 44px).

statusMessageView stays visible for 2 seconds

The UITableViewController animates a scroll up to it's original position, effectively "tucking" statusMessageView away. (like UIRefreshControl, but animated with code)

Note: This statusMessageView will be used in conjunction with a UIRefreshControl, so it needs to "go away" after it is displayed so that the UIRefreshControl can be used normally

I have looked at other 3rd party "pull to refresh" controllers, but I think that is overkill due to the fact I am using UIRefreshControl

回答1:

It seems like the other answers here are providing solutions for a notification that drops down below the UINavigationBar. If you're still looking for a solution that sits in the scrollview of the UITableView, then I would add a custom table header (not section header) to the table view.

Here are the rough steps necessary to accomplish this:

1. Create the initial header view on load

I typically use a UIViewController subclass that owns a UITableView instance variable (instead of using a UITableViewController), but you should be able to accomplish this with either set up. In your tableview set up code (probably in viewDidLoad), where you set things like backgroundColor, contentInset, separatorStyle, etc, create a UILabel that will become your header. Then set this UILabel to be the tableHeaderView of your tableView. Of course, if you're looking to make something a bit more complicated for this "notification section", feel free to make it a UIView with a nested UILabel + something else. So something like:

UILabel *headerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.tableView.bounds.size.width, 44.0f)];
headerLabel.backgroundColor = [UIColor clearColor]; // Just in case you have some fancy background/color in your table view
headerLabel.textAlignment = UITextAlignmentCenter;
headerLabel.font = ....
headerLabel.textColor = ....
headerLabel.text = @"You are an awesome user!";
self.tableView.tableHeaderView = headerLabel;

2. Set your tableview to load "normally" (ie. not show header)

Again, inside viewDidLoad, you need to set your tableview's contentOffset and alwaysBounceVertical properly to hide this header view on load. contentOffset set to the height of the header will start the tableview's y coordinate right below the header. alwaysBounceVertical set to YES will allow your tableview to behave correctly even if your tableview's contentsize is less than your screen size. So something like:

self.tableView.contentOffset = (CGPoint){0.0f, 44.0f};
self.tableView.alwaysBounceVertical = YES;

3. Add the slide down and slide away

Ok, now there are a couple of ways you can do this. On viewDidAppear, you can create a chained UIView animation where the first animation slides the tableview down (ie. sets contentOffset to {0.0f, 0.0f}) is delayed by one second and the second animation slides the tableview back up (ie. sets contentOffset to {0.0f, 44.0f}) is delayed by two seconds. Or you can use GCD and schedule the two animations as async + delayed blocks. Either way is fine (and there are probably two or three other good ways to accomplish this), but just to get the idea across... you can chain the animation like this:

__weak MyCustomViewController *me = self;
[UIView 
    animateWithDuration:0.4f 
    delay:1.0f 
    options:UIViewAnimationOptionAllowUserInteraction 
    animations:^
    { 
        me.tableView.contentOffset = (CGPoint){0.0f, 0.0f}; 
    } 
    completion:^(BOOL finished)
    { 
        if (me.tableView) 
        { 
            [UIView 
                animateWithDuration:0.4f 
                delay:2.0f 
                options:UIViewAnimationOptionAllowUserInteraction 
                animations:^
                { 
                    me.tableView.contentOffset = (CGPoint){0.0f, 44.0f}; 
                } 
                completion:^(BOOL finished)
                { 
                    if (me.tableView) 
                    {  
                        me.tableView.tableHeaderView = nil; // If you want to completely get rid of this notification header
                        me.tableView.contentOffset = (CGPoint){0.0f, 0.0f}; // I'm unsure if this will cause the tableview to jump... if it does, you can animate the headerview away instead of animating the tableview up to hide the header, then setting header to nil and reseting the contentOffset

                        // or

                        me.tableView.tableHeaderView.hidden = YES; // If you want to just hide the header
                    } 
                }]; 
        } 
    }];

I wrote the sample code without testing any of it... hehe, so it probably won't work as it. But happy to help you flesh this out if you need more help! Good luck.

Update: Allowing user scrolling to cancel animation

I'm not too sure what you want the user interaction on the table to do to the animation. If you just want the animation to be canceled when the user starts scrolling, then I would use GCD (see code below). But I can see other ways you can have the animation work with user touch, so it depends on what you're looking for. In any case, let's say any user touch should disable the next scheduled animation then it could be achieved using two functions like:

- (void)scheduleShowHeaderAnimation
{
    __weak MyCustomViewController *me = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0f * NSEC_PER_SEC), dispatch_get_main_queue(), ^ // Start animating after 1 sec delay
    {
        if (me.tableView) // Make sure the tableview is still around
        {
            if (! me.tableView.tracking &&   // Don't show animation if user has begun to touch contentview
                ! me.tableView.dragging &&   // Don't show animation if user has begun to drag contentview
                ! me.tableView.decelerating) // Don't show animation if dragging happened and scrollview is starting to decelerate
            {
                [UIView
                    animateWithDuration:0.4f
                    delay:0.0f
                    options:UIViewAnimationOptionAllowAnimatedContent // This will make sure user can interact with tableview while animation is going on
                    animations:^
                    {
                        me.tableView.contentOffset = (CGPoint){0.0f, 0.0f};
                    }
                    completion:^(BOOL finished)
                    {
                        [me scheduleHideHeaderAnimation];
                    }];
            }
        }
    });
}

- (void)scheduleHideHeaderAnimation
{
    __weak MyCustomViewController *me = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2.0f * NSEC_PER_SEC), dispatch_get_main_queue(), ^ // Start animating after 2 secs delay
    {
        if (me.tableView) // Make sure the tableview is still around
        {
            if (! me.tableView.tracking &&   // Don't show animation if user has begun to touch contentview
                ! me.tableView.dragging &&   // Don't show animation if user has begun to drag contentview
                ! me.tableView.decelerating) // Don't show animation if dragging happened and scrollview is starting to decelerate
            {
                [UIView
                    animateWithDuration:0.4f
                    delay:0.0f
                    options:UIViewAnimationOptionAllowAnimatedContent // This will make sure user can interact with tableview while animation is going on
                    animations:^
                    {
                        me.tableView.contentOffset = (CGPoint){0.0f, 44.0f};
                    }
                    completion:^(BOOL finished)
                    {
                        if (me.tableView)
                        {
                            me.tableView.tableHeaderView = nil; // If you want to completely get rid of this notification header
                            me.tableView.contentOffset = (CGPoint){0.0f, 0.0f}; // I'm unsure if this will cause the tableview to jump... if it does, you can animate the headerview away instead of animating the tableview up to hide the header, then setting header to nil and reseting the contentOffset

                            // or

                            me.tableView.tableHeaderView.hidden = YES; // If you want to just hide the header
                        }
                    }];
            }
        }
    });
}

I would call scheduleShowHeaderAnimation in the viewDidAppear.

Then to support hiding the header when the user has already scrolled the tableview down, I would implement either - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate or - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView of UIScrollViewDelegate and add something like:

if (self.tableView.tableHeaderView != nil)
{
    self.tableView.tableHeaderView = nil;
}

// or

if (self.tableView.tableHeaderView.hidden == NO)
{
    self.tableView.tableHeaderView.hidden = YES;
}

If there's more complicated interactions you want to support, or if you want the animation to respond to the user touch in different ways, you might need to override other UIScrollViewDelegate methods and when the user begins to interact with the scrollview (which is the parent class of the table view), then change the behavior of the animation.

Does this get you closer to what you're looking for?



回答2:

besides the answer of @Khawar , there is another choice:
https://github.com/toursprung/TSMessages



回答3:

You can use following library for this purpose. You can set the autohide time duration as per your needs. You can also customise its appearance.

https://github.com/tciuro/NoticeView



回答4:

It can be done in a very simple way: you just create and insert a subview into your refresh control:

- (void)viewDidLoad {
    [super viewDidLoad];
    ...
    UIView *smView = [self statusMessageView];
    [self.refreshControl insertSubview: smView atIndex: 0];

    // to show and hide the view inserted in refresh control
    // With this transparent loader color even the middle area is not covered
    // so you can have all the space to show your message
    self.refreshControl.tintColor = [UIColor colorWithWhite:1.0 alpha:0.0];
    [self.refreshControl beginRefreshing];

    [self afterDelay: 2.0  performBlock: ^(){
        [self.refreshControl endRefreshing];
        // this delay is needed to prevent loader animation becoming visible
        // while the refresh control is being hidden
        [self afterDelay: 0.25  performBlock: ^(){
            self.refreshControl.tintColor = nil;
        });
    });
    ...
}

-(void)afterDelay: (float)seconds  performBlock: (void (^)())block  {
    dispatch_time_t popTime =
        dispatch_time(DISPATCH_TIME_NOW, (int64_t)(seconds * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_main_queue(), block);
}

-(UIView)statusMessageView {
    ...
    return view;
}

View size you need is 320x43px (if it is higher the bottom of it will be visible with refresh control hidden), the middle area (approximately 35px) will be covered by the loader animation (but not when you initially show the message).

I use UIImageView to show application logo there (it's even simpler: see here).

I would probably prefer (as a user) not to see this message before I start pulling. When I start pulling I will see that no update is needed before refresh starts (it starts when you pull down approximately twice more than the height of this subview, so user will have time to see what's there and if refresh is needed). If there are unread items in the table (tweets, posts, whatever), you can show the number of unread items in that view.

But it's a matter of personal preference, the way it is done it seems to achieve exactly what you want in a very simple way. I tested, it all works.