Is it possible to have dynamically coloring statusBar
which is in the new Apple Music app ?
Edit:
The new Apple Music app in iOS 8.4 has this feature.
Edit 2:
Apple documentation does not seem to let us use it right now (iOS 8.4
). Will be available probably in the future with iOS 9
.
Edit 3:
Does not seems to be available in iOS 9
yet.
Apple in June 2021 upgraded its entire music catalog to Lossless Audio with the ALAC (Apple Lossless Audio Codec) that preserves the details in the original audio file. Apple Music subscribers will be able to hear songs exactly as the artists recorded them in the studio.
Free 3 months of Apple Music, and $9.98/month thereafter. Listen to 75 million songs data-free on your iOS and Android devices. Free 3 months of Apple Music, and $9.98/month thereafter.
Pair your eligible audio device to your iPhone or iPad. Open the Apple Music app on your iPhone or iPad and sign in with your Apple ID. If the offer doesn't appear immediately after launching the app, go to the Listen Now tab where it will appear. Tap Get 6 months free.
Apple Music's library has over 90 million songs. Oh, and you can also watch music videos without ads, and check out Apple's exclusive original content. Apple Music used to have an artist-based social networking feature called Connect. Artists were able to share special content with fans through Connect.
I am 99.99% sure this cannot be done using public API (easily), because I tried myself almost everything there is (i personally also don't think it is some magical method of their status bar, but instead, their application is able to retrieve status bar view and then just apply mask to it).
What I am sure of is that you can do your own StatusBar and there is MTStatusBarOverlay library for that, unfortunately very old one so I can't really tell if that works but it seems that there are still people who use it.
But using the way library does it, I think there might be solution that sure, requires a lot of work, but is doable, though not "live". In a nutshell you would do this:
Now you should be able to scroll properly and change the color properly. The only problem that it leaves is that status bar is not alive, but is it really? once you scroll out, you immediately remove your overlay, letting it to refresh. You will do the same when you scroll to the very top, but in that case, you change color of the status bar to white (no animation), so it fits your state. It will be not-live only for a brief period of time.
Hope it helps!
Iterating upon Jiri's answer, this will get you pretty close. Substitute MTStatusBarOverlay with CWStatusBarNotification. To handle the modal transition between view controllers, I'm using MusicPlayerTransition. We're assuming an imageView: "art" in self.view with frame:CGRect(0, 0, self.view.bounds.size.width, self.view.bounds.size.width). Needs a little massaging, but you get the gist. Note: Though we're not "live," the most we'll ever be off is one second, and battery color is not preserved. Also, you'll need to set the animation time in CWStatusBarNotification.m to zero. (notificationAnimationDuration property).
#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;
}
To keep your status bar screenshots in sync with the actual status bar, setup your timer. Fire it in viewWillAppear, and kill it in 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;
}
Because we have a strong reference to the timer and setup and invalidation happens on the same thread, there's no worrying about the timer failing to invalidate. The final result should look something like this:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With