【问题标题】:The same dynamic status bar as is in the new Apple Music app与新 Apple Music 应用程序中相同的动态状态栏
【发布时间】:2015-10-01 10:27:22
【问题描述】:

是否可以在新的 Apple Music 应用程序中动态着色 statusBar

编辑:

iOS 8.4 中的新 Apple Music 应用具有此功能。

  • 打开应用程序。
  • 选择并播放歌曲(状态栏为白色)
  • 向下滑动播放器控制器以查看“我的音乐”控制器(它有黑色状态栏,也许您必须返回导航层次结构)。
  • 现在只需向上/向下滑动即可查看动态状态栏的变化。

编辑 2:

Apple 文档似乎不允许我们现在使用它 (iOS 8.4)。将来可能会通过iOS 9 提供。

编辑 3: iOS 9 中似乎还没有。

【问题讨论】:

  • 考虑私有API
  • 图片:顶部白色部分是第一个VC,带有白色statusBar和专辑封面的部分是另一个VC(在第一个VC之上)。
  • 问题是状态栏上半部是深色,下半部是浅色?
  • 不,这不是问题。 iOS 8.4 中的新 Apple Music 应用程序具有此功能。您所要做的就是打开应用程序,选择一首歌曲(状态栏为白色)并向下滑动此控制器以查看“我的音乐”控制器(它有黑色状态栏)。现在只需向上/向下滑动即可查看动态状态栏的变化。
  • 感谢更新信息。据我所知,这个问题没有简单的解决方案。您可以查看此项目以供参考,它使用类似于我在答案中建议您的方法(但实现可滚动状态栏行为)。作者拍摄状态栏的快照并将其作为图像添加到假状态栏窗口。 github.com/Antondomashnev/UIViewController-ScrollingStatusBar

标签: ios objective-c user-interface dynamic statusbar


【解决方案1】:

我 99.99% 确定这不能使用公共 API(轻松)完成,因为我尝试了几乎所有的东西(我个人也不认为这是他们状态栏的一些神奇方法,而是他们的应用程序能够检索状态栏视图,然后对其应用掩码)。

我确信你可以做你自己的 StatusBar 并且有 MTStatusBarOverlay 库为此,不幸的是非常旧的,所以我不能确定它是否有效,但似乎还有人在使用它。

但是使用库的方式,我认为可能有解决方案,确实需要大量工作,但可行,虽然不是“活的”。简而言之,你会这样做:

  • 截取前 20 个像素(状态栏)
  • 从该屏幕截图中,remove everything that is not black(您可以对其进行改进,使其搜索黑边,这样您可以保留绿色电池和透明度)这将使您的覆盖蒙版和假状态栏
  • 覆盖状态栏:背景视图掩盖实际状态栏,以及您刚刚创建的 alpha 图像
  • Apply mask to that image,所有被遮盖的东西都会变成白色的阴影
  • 根据用户滚动更改遮罩的高度

现在您应该能够正确滚动并正确更改颜色。它留下的唯一问题是状态栏不存在,但它真的存在吗?一旦你滚动出去,你会立即移除你的覆盖,让它刷新。当您滚动到最顶部时,您将执行相同的操作,但在这种情况下,您将状态栏的颜色更改为白色(无动画),因此它适合您的状态。它只会在短时间内不存在。

希望对你有帮助!

【讨论】:

  • 对于否决这个答案的人,您能否提供您对为什么它不好的意见,以便我们对其进行迭代?谢谢!
  • 奖励了这个答案,因为它提供了私人 api 意见并提供了一个例子(即使它令人痛苦)。真正的答案是我们都可能不得不等待这个。
  • 他们也在使用私有 API 来制作动画输入/输出表视图 AZ 索引,这绝对不是公开的,Apple Music 中也有做私有 UI 事情的先例 :(
【解决方案2】:

迭代 Jiri 的答案,这将使您非常接近。将MTStatusBarOverlay 替换为CWStatusBarNotification。为了处理视图控制器之间的模态转换,我使用了MusicPlayerTransition。我们假设 self.view 中的 imageView: "art" 带有 frame:CGRect(0, 0, self.view.bounds.size.width, self.view.bounds.size.width)。需要一点按摩,但你明白了要点。注意:虽然我们不是“活”的,但我们最多只能关闭一秒钟,并且不会保留电池颜色。此外,您需要将 CWStatusBarNotification.m 中的动画时间设置为零。 (notificationAnimationDuration 属性)。

#import "CWStatusBarNotification.h"

#define kStatusTextOffset       5.4  // (rough guess of) space between window's origin.y and status bar label's origin.y

@interface M_Player () <UIGestureRecognizerDelegate> 

@property (retain) UIView *fakeStatusBarView;
@property (retain) CWStatusBarNotification *fakeStatusBar;
@property (retain) UIImageView *statusImgView;
@property (retain) UIImageView *statusImgViewCopy;
@property (retain) UIWindow *window;
@property (strong, nonatomic) NSTimer *statusTimer;

@end

@implementation M_Player
@synthesisze fakeStatusBarView, fakeStatusBar, statusImgView, statusImgViewCopy, window, statusTimer;

-(void)viewDidLoad{

    self.window = [[UIApplication sharedApplication] delegate].window;

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleStatusBarDrag:)];
    pan.delegate = self;
    [self.view addGestureRecognizer:pan];
}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];

    if (!fakeStatusBar){
        [self buildFakeStatusBar];
    }

    if (!statusTimer) {
        [self setupStatusBarImageUpdateTimer];
    }

     // optional
    [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
    [self setNeedsStatusBarAppearanceUpdate]; 


-(void)viewDidDisappear:(BOOL)animated{
   [super viewDidDisappear:animated];

   [self destroyStatusBarImageUpdateTimer];

}

-(void)destroyFakeStatusBar{
    [statusImgView removeFromSuperview];
    statusImgView = nil;
    [fakeStatusBarView removeFromSuperview];
    fakeStatusBarView = nil;
    fakeStatusBar = nil;
}

-(void)buildFakeStatusBar{
    UIWindow *statusBarWindow =  [[UIApplication sharedApplication] valueForKey:@"_statusBarWindow"];  // This window is actually still fullscreen. So we need to capture just the top 20 points.

    UIGraphicsBeginImageContext(self.view.bounds.size);
    [statusBarWindow.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGRect rect = CGRectMake(0, 0, self.view.bounds.size.width, 20);
    CGImageRef imageRef = CGImageCreateWithImageInRect([viewImage CGImage], rect);
    UIImage *statusImg = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);

    statusImg = [statusImg imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];  // This allows us to set the status bar content's color via the imageView's .tintColor property

    statusImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)];
    statusImgView.image = statusImg;
    statusImgView.tintColor = [UIColor colorWithWhite:0.859 alpha:1.000];  // any color you want

    statusImgViewCopy = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)];
    statusImgViewCopy.image = statusImg;
    statusImgViewCopy.tintColor = statusImgView.tintColor;


    fakeStatusBarView = nil;
    fakeStatusBar = nil;
    fakeStatusBarView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 20)];
    [fakeStatusBarView addSubview:statusImgView];

    fakeStatusBar = [CWStatusBarNotification new];
    fakeStatusBar.notificationStyle = CWNotificationStyleStatusBarNotification;
    [fakeStatusBar displayNotificationWithView:fakeStatusBarView forDuration:CGFLOAT_MAX];
}

-(void)handleStatusBarDrag:(UIPanGestureRecognizer*)gestureRecognizer{
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {

    }

    if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
        CGPoint convertedPoint = [self.window convertPoint:art.frame.origin fromView:self.view];
        CGFloat originY = convertedPoint.y - kStatusTextOffset;

        if (originY > 0 && originY <= 10) {  // the range of change we're interested in
            //NSLog(@"originY:%f statusImgView.frame:%@", originY, NSStringFromCGRect(statusImgView.frame));

            // render in context from new originY using our untouched copy as reference view
            UIGraphicsBeginImageContext(self.view.bounds.size);
            [statusImgViewCopy.layer renderInContext:UIGraphicsGetCurrentContext()];
            UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            CGRect rect = CGRectMake(0, kStatusTextOffset + originY, self.view.bounds.size.width, 20);
            CGImageRef imageRef = CGImageCreateWithImageInRect([viewImage CGImage], rect);
            UIImage *statusImg = [UIImage imageWithCGImage:imageRef];
            CGImageRelease(imageRef);

            statusImgView.image = statusImg;
            statusImgView.transform = CGAffineTransformMakeTranslation(0, kStatusTextOffset + originY);

        }

       // destroy
        if (originY > 90) {
            [self destroyFakeStatusBar];
        }
    }

    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){

    }
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}

要使您的状态栏屏幕截图与实际状态栏保持同步,请设置您的计时器。在 viewWillAppear 中触发它,并在 viewDidDisappear 中杀死它。

-(void)setupStatusBarImageUpdateTimer{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^(){
            // main thread
            if (!statusTimer) {
                statusTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleStatusTimer:) userInfo:nil repeats:YES];
                [[NSRunLoop currentRunLoop] addTimer:statusTimer forMode:NSRunLoopCommonModes];
            }
        });
    });
}

-(void)destroyStatusBarImageUpdateTimer{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_main_queue(), ^(){
            // main thread
            [statusTimer invalidate];
            statusTimer = nil;
        });
    });
}

-(void)handleStatusTimer:(NSTimer*)timer{
    UIWindow *statusBarWindow =  [[UIApplication sharedApplication] valueForKey:@"_statusBarWindow"];

    UIGraphicsBeginImageContext(CGSizeMake(self.view.bounds.size.width, 20));
    [statusBarWindow.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGRect rect = CGRectMake(0, 0, self.view.bounds.size.width, 20);
    CGImageRef imageRef = CGImageCreateWithImageInRect([viewImage CGImage], rect);
    UIImage *statusImg = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);

    statusImg = [statusImg imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
    statusImgViewCopy.image = statusImg;
}

因为我们对定时器有很强的引用,并且设置和失效发生在同一个线程上,所以不用担心定时器失效。 最终结果应如下所示:

【讨论】:

    【解决方案3】:

    乍一看,它看起来像是对状态栏快照的操作,但状态栏在两端都是实时的,所以情况并非如此。

    乍一看,它看起来像是 iOS 8.4 中引入的一些新 api,但在查看了该 api 后,我找不到与此相关的任何内容。

    在我看来,苹果会在她自己的应用程序中使用私有 API,这让我觉得很奇怪。这会给开发人员带来一些非常糟糕的例子,但话又说回来,没有任何公开的东西可以让您在实时状态栏上拥有两种样式。

    这给我们留下了私有 api 或黑魔法。

    【讨论】:

    • 如果 Apple 使用私有 API,为什么会很奇怪?
    • 我确信苹果在后台使用了很多私有的东西,但是对于开发人员显然无法使用的视觉效果.. 对我来说有点不妥。
    【解决方案4】:

    考虑如何在没有私有 API 的情况下实现这一点。

    我认为可能有第二个 UIWindow 覆盖您的状态栏的解决方案。

    Adding view on StatusBar in iPhone

    也许可以不断地将状态栏截图(从您的主窗口截取)到图像,对其应用一些过滤器并在您的第二个窗口(在“真实”状态栏上方)显示这个“假状态栏图像”。

    你可以用第二个“假”状态栏做你想做的事。

    【讨论】:

      猜你喜欢
      • 2020-07-24
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多