How do I change UIImageView image in view controller in traitCollectionDidChange - ios

I want the image in my UIImageView to us a different image file that's cropped to correctly fill the landscape mode upon the orientation changing from portrait to landscape. All of the following code is defined in a class that extends UIViewController.
The following function is definitely being called when the phone rotates because my printf function is printing to the output.
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
printf("Orientation Change!\n");
UIImage * newImage = [UIImage imageNamed: #"landscape-image"];
[self.imageView setImage:newImage];
}
}
I defined the property imageView in my .h file as follows:
#property (nonatomic, nonnull, readonly) UIImageView * imageView;
And I initialize the imageView as follows:
UIImage * image = [UIImage imageNamed:#"portrait-image"];
UIImageView * _imageView = [[UIImageView alloc] initWithImage:image];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
_imageView.translatesAutoresizingMaskIntoConstraints = NO;
[pageView addSubview:_imageView];
This does not work for me though. When I change the orientation, the imageView image stays the same and zooms in like it normally does. Since the traitCollectionDidChange function is being called when the phone rotates, I assume the issue must be with how I'm changing the image. I'm relatively new to iOS development so I could just be missing something important for updating UIImageViews. Any help is appreciated.

When you create the imageView using UIImageView * _imageView = [[UIImageView alloc] initWithImage:image];, you are shadowing the automatically synthesised variable _imageView, with a new variable of the same name.
The newly created UIImageView instance is therefore not assigned to the imageView property. As a result, when the device is rotated and the second method is run, self.imageView is nil, and you call to [self.imageView setImage:newImage] does nothing.
To solve it, all you need to do replace UIImageView * _imageView = [[UIImageView alloc] initWithImage:image]; with _imageView = [[UIImageView alloc] initWithImage:image];
More on the messiness of automatically synthesised properties in Objective-C in this answer When should I use #synthesize explicitly?
EDIT --
In addition, the - (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection may be called at any time, and initially, so you want to detect the orientation in that method, and not just set the image to your landscape image.
(and on this - ou may want to re-evaluate your requirements regarding 'landscape' vs 'portrait' image because iOS apps can run in a variety of environment, iPad / iPhone / compact.. it is complicated. https://developer.apple.com/documentation/uikit/uitraitcollection)
To complete the answer, this should work on iPhone
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| (self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass)) {
if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
[self.imageView setImage:[UIImage imageNamed: #"landscape-image"]];
} else if (self.traitCollection.verticalSizeClass == UIUserInterfaceSizeClassRegular
&& self.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact) {
[self.imageView setImage:[UIImage imageNamed: #"portrait-image"]];
}
}
}

Related

UIImageView not showing in app

I have a UIImageView which is displayed using the following code:
profileImage = [self getImageFromUrl:#"http://i.imgur.com/l0HVdmR.png"];
if (profileImage == nil) {
NSLog(#"SourceImage is nil");
} else {
NSLog(#"SourceImage isn't nil");
}
profileImageView = [[UIImageView alloc] init];
profileImageView.image = profileImage;
[profileImageView setFrame:CGRectMake((screenWidth/2)-(profileDiameter/2), profileDiameter/2, profileDiameter, profileDiameter)];
[profileImageView setContentMode:UIViewContentModeScaleToFill];
profileImageView.userInteractionEnabled = isEditing;
NSLog(#"Online image used");
if (profileImageView == nil) {
NSLog(#"ImageView is nil");
} else {
NSLog(#"ImageView isn't nil");
}
Later on I use the code [mainScrollView addSubview:profileImageView to add the UIImageView to the UIScrollView I have set up. The UIImageView used to correctly display this image without any trouble. However, the UIImageView has suddenly stopped displaying altogether, and I'm unsure of what change elsewhere in my code caused this.
The framing isn't to blame. The variables are the correct values and changing them didn't solve the issue. The NSLogs show both the UIImage and UIImageView to not be nil. If it helps, the UITapGestureRecognizer I have set up on the UIImageView does not register taps in the area in which the image should be - This may provide some clue regarding the symptoms of the bug.
All help appreciated.
Check whether the value of profileDiameter was initialised or not. Also addSubview must be used.
Kindly upload some more details regarding how profileDiameter is found out.

The same dynamic status bar as is in the new Apple Music app

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.
Open the app.
Select and play a song (status bar is white)
Swipe player controller down to see "My music" controller (it has black status bar, maybe you will have to go back in navigation hierarchy).
Now just swipe up/down to see dynamic status bar changes.
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.
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:
Take screenshot of top 20 pixels (status bar)
From that screenshot, remove everything that is not black (you can improve it so it searches for black edges, this way you can preserve green battery and transparency) This will make your overlay mask and also fake status bar
Overlay statusbar with : background view masking actual status bar, and the alpha-image you just created
Apply mask to that image, everything that is masked will change color to shades of white
Change height of the mask depending on user scroll
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:
At first glance it looked like a manipulation of a snapshot from the status bar but the status bar is live on both ends so that's not the case.
At second glance it looked like some new api that was introduced in iOS 8.4 but after reviewing the api I couldn't find anything related to that.
It seems very odd to me that apple would use private apis in her own app. This would results some really bad example for developers but then again, there is nothing public that will let you have two styles on your live statusbar.
This leaves us with private api or black magic.
Thinking about how to implement this without private APIs.
I think there may be a solution with second UIWindow overlaying your statusBar.
Adding view on StatusBar in iPhone
Maybe it's possible to make screenshots of status bar constantly (taken from your main Window) to the image, apply some filter on it and display this 'fake statusbar image' on your second window (above 'real' statusBar).
And you can do what you want with the second "fake" statusbar.

Destroy UIImageView objects after they move out of bounds

In my application I have some images falling from above inside a UIView. The UIImageView objects are created dynamically and made to move downwards. My question is, do the objects destroy themselves after they move off below the screen area? Or should I do it manually to improve performance?
Once there are no more strong references to an object, it will be deallocated. The two references you probably need clear will be the variable pointer, and the superview's reference to it. So you'd need to do something like:
[imageView removeFromSuperView];
imageView = nil;
It's possible there are more as you did not provide any code (if for instance you have a pointer to the object in an array, you'd need to remove that too).
Removing from superview is all that's needed if you don't have any other pointers to the image view (in ARC). The most performant pattern is to keep a pool of pointers to offscreen image views. In pseudo code:
// this assumes the number onscreen is relatively small, in the tens or low hundreds
// this works better when the number on screen is relatively constant (low variance)
// say the animation looks something like this:
- (void)makeAnImageFall:(UIImage *)image {
CGRect startFrame = // probably some rect above the superview's bounds
UIImageView *imageView = [self addImageViewWithImage:image frame:frame]; // see below
[UIView animateWithDuration:1.0 animations:^{
imageView.frame = // probably some rect below the superview's bounds
} completion:^(BOOL finished) {
[self removeImageView:imageView]; // see below
}];
}
Then these methods do everything to handle the pool:
- (UIImageView *)addImageViewWithImage:(UIImage *)image frame:(CGRect)frame {
UIImageView *imageView;
// assume you've declared and initialized imageViewPool as an NSMutableArray
// or do that here:
if (!self.imageViewPool) self.imageViewPool = [NSMutableArray array];
if (self.imageViewPool.count) {
imageView = [self.imageViewPool lastObject];
[self.imageViewPool removeLastObject];
} else {
imageView = [[UIImageView alloc] init];
}
imageView.image = image;
imageView.frame = frame;
[self.view addSubview:imageView];
return imageView;
}
- (void)removeImageView:(UIImageView *)imageView {
imageView.image = nil;
[self.imageViewPool addObject:imageView];
[imageView removeFromSuperview];
}
The nice idea is that we avoid the relatively expensive churn of create-destroy of many image views. Instead, we create them (lazily) when they're first needed. Once we hit the high water mark of image views, we don't ever allocate another.

How can I display a UIImage in a UIImageView?

I am playing around with Xcode 5 and storyboarding. I'm at the point where I have captured a UIImage using the camera, but I can't figure out how to display the image on the phone after I've captured it. I have specified an IBOutlet UIImageView and set it's value to the image that I captured from the camera, but that doesn't seem to do anything at all.
This is my interface:
#interface HCViewController ()
#property (nonatomic, retain) IBOutlet UIImageView *image;
#end
And this is my didFinishPickingWithMediaInfo method:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Get the picture from the camera
UIImage *imageFromCamera = [info objectForKey:UIImagePickerControllerEditedImage];
// Set the IBOutlet UIImageView to the picture from the camera
// This does nothing as far as I can tell
self.image = [[UIImageView alloc] initWithImage:imageFromCamera];
// Let's take a closer look at this
NSLog(#"%#", self.image);
// Close the camera view controller
[self dismissViewControllerAnimated:YES completion:nil];
}
This is what I see in the logger for self.image:
2013-09-10 17:17:45.169 YourMom[6136:60b] <UIImageView: 0x1556fdc0; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x155d5b10>>
My storyboard has a View Controller with two different scenes that can be swiped back and forth. Both of the scenes have a "View" with an "Image View" as a sub-item. The Image Views each have "Referencing Outlets" that seem to be connected to the image variable that I defined in my interface. However, simply setting the value of image doesn't change the phone display.
After reading this SO question: How can I change the image displayed in an UIImageView programmatically? I tried [self.image setImage:image], but that didn't appear to do anything either. How do I tell Xcode that I want image to show up in the view?
Your mistake is in this:
self.image = [[UIImageView alloc] initWithImage:imageFromCamera];
Because you are using Storyboard your UIImageView will be initialized for you. By executing this line of code you are throwing away an old UIImageView and replacing it with a new UIImageView that has no frame.
You just need to do this:
[self.image setImage:imageFromCamera]
Also, you might be getting the wrong image from info. Try UIImagePickerControllerOriginalImage instead of UIImagePickerControllerEditedImage.
Hope this is helpful, cheers!
UIImage View has an property called image.
This property is of type UIImage.
So if you have an UIImage, then you can set the property as follows:
imageView.image = image;
// this is your delegate method to set the image taken from your camera
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
// Get the picture from the camera
UIImage *imageFromCamera = [info objectForKey:UIImagePickerControllerEditedImage];
// alloc image view as you have to set it image on it
UIImageView *imgView = [[UIImageView alloc]init];
imgView.image = imageFromCamera;
// you can also set the image on button like this
UIImage *imageFromCamera = [info objectForKey:UIImagePickerControllerEditedImage];
// imgBtn is UIButton property
[self.imgBtn setImage:imageFromCamera forState:UIControlStateNormal];
// Let's take a closer look at this
NSLog(#"%#", self.image);
// Close the camera view controller
[self dismissViewControllerAnimated:YES completion:nil];
}
You can try this code to display your image.
imageView.image : UIImage = UIImage(named: "YourImageName")
Hope this can help you with your problems.

UIImageView rotating but not properly repositioning

I am building an app that displays an image in landscape and portrait modes. Rotating works perfectly. The image is also perfectly positioned in landscape mode. However it keeps its landscape coordinates in portrait, which misplace it as a result. Please find my code below. Could you let me know what I'm missing? Is there also a way to achieve this strictly from a Xib file?
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self navigationController] setNavigationBarHidden:YES animated:NO];
UIImage *startImage = [UIImage imageNamed:#"title.png"];
UIImageView *startImageView = [[UIImageView alloc] initWithImage:startImage];
if (curOrientation == UIInterfaceOrientationPortrait || curOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[startImageView setFrame:CGRectMake(-128, 0, 1024, 1024)];
}else{
[startImageView setFrame:CGRectMake(0, -128, 1024, 1024)];
}
[self.view addSubview:startImageView];
}
Currently you are only calling this code when the view is first loaded. You actually need to call it
whenever the view appears onscreen (in case the device was rotated while it was offscreen)
whenever the device is rotated
but you should keep the view creation code in viewDidLoad, as you only want to create it once.
Make a property to keep a pointer to the view so that you can refer to it from all of these places in your codeā€¦
#property (nonatomic, weak) UIImageView* startImageView;
Create it in viewDidLoad (but don't worry then about the geometry, as you can do this in viewWillAppear):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[[self navigationController] setNavigationBarHidden:YES animated:NO];
UIImage *startImage = [UIImage imageNamed:#"title.png"];
UIImageView *startImageView = [[UIImageView alloc] initWithImage:startImage];
self.startImageView = startImageView;
[self.view addSubview:startImageView];
}
Make a generic orientation method:
- (void) orientStartImageView
{
UIInterfaceOrientation curOrientation = [UIApplication sharedApplication].statusBarOrientation;
if (curOrientation == UIInterfaceOrientationPortrait || curOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[self.startImageView setFrame:CGRectMake(-128, 0, 1200, 1200)];
}else{
[self.startImageView setFrame:CGRectMake(0, -128, 1200, 1200)];
}
}
Call it from viewWillAppear (triggered every time the view comes onscreen):
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self orientStartImageView];
}
Call it from viewWillLayoutSubviews (triggered every time the view IS onscreen and the device rotates):
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
[self orientStartImageView];
}
By the way, I am not sure your frames are correct - in portrait you are shifting the left edge offscreen, in landscape you are shifting the top edge offscreen. Is that what you want? It may well be that you can achieve what you want in Interface Builder, but it is not clear from your code what that is - maybe you could post a picture. Also check that you have Autolayout disabled (checkbox in Interface Builder's file inspector) to simplify issues.
update
You may be able to do this from the Xib with no code: centre the imageView in it's superView, set it's size to your final size (eg 1200x1200), disable Autolayout, deselect all springs and struts, set your View Mode appropriately (eg center or scaleToFill)

Resources