IOS Tab Bar height issue on iPhone X - objective-c

I have tried many solutions including This Solution. So Please read full question before flagging it as duplicate. For iPhone X I have use Safe Area guide to support iPhone X design. Everything is fine except for Tab Bar I have double check on its safe area constraints but still after running it's items got scattered.
But please not that on height of tab bar is constant and correct only items in it are disturb.
Here is image of my story board please not its constraints and its adjustment.
As per my knowledge everything is everything is right... but for sure I am missing something, please check and any suggestion will be a great help. But please not I have tried almost every way which is mentioned on stack or first few links of google.
Here is my code which I am using to populate items' images.
UIColor *color = [UIColor colorWithRed:192.0f/255.0f green:41.0f/255.0f blue:66.0f/255.0f alpha:1.0f];
NSDictionary *textColors = [NSDictionary dictionaryWithObjectsAndKeys:color, NSForegroundColorAttributeName, nil];
for(UITabBarItem *tab in self.tabBar.items)
{
[tab setTitleTextAttributes:textColors forState:UIControlStateNormal];
[tab setTitleTextAttributes:textColors forState:UIControlStateSelected];
}
self.tabBar.itemPositioning = UITabBarItemPositioningAutomatic;
[self.tabBar.items[0] setImage:[[UIImage imageNamed:#"search_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[1] setImage:[[UIImage imageNamed:#"user_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[2] setImage:[[UIImage imageNamed:#"call_icon"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[self.tabBar.items[3] setImage:[[UIImage imageNamed:#"services_icon"]
imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];

at first I tried almost every possible solution but that didn't work at all, but then at the end somehow I added some negative margin with bottom. Let's say "-1" and then everything started working like charm. I also tried setting positive margin just for experiment. But that didn't work, I concluded only negative number works. This must be an issue in UIKit.
Set bottom constraint with safe area as any negative number will solve this issue.
check its bottom constraint.

Try using the method recommended in the documentation to set the items.
To configure tab bar items directly, use the setItems:animated: method
of the tab bar itself.
- (void)setItems:(NSArray<UITabBarItem *> *)items animated:(BOOL)animated;
Do something like this.
UIIImage *image0 = [UIImage imageNamed:#"search_icon"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
UITabBarItem *item0 = [[UITabBarItem alloc] initWithTitle:self.tabBar.items[0].title image:image0 tag:0];
// And so on or in a loop
NSArray<UITabBarItem *> *items = #[ item0, ]; // Add all items here
[self.tabBar setItems:items animated:NO];
Read more here
https://developer.apple.com/documentation/uikit/uitabbar?language=objc

Just put your UITabBar inside a UIView!
This is working for me.

This will definitely work.
override func viewDidLoad() {
super.viewDidLoad()
let numberOfItems = CGFloat(tabBArView.items!.count)
let tabBarItemSize = CGSize(width: tabBArView.frame.width / numberOfItems, height: 48)
tabBArView.selectionIndicatorImage = UIImage.imageWithColor(color: UIColor.orange, size: tabBarItemSize).resizableImage(withCapInsets: UIEdgeInsets.zero)
tabBArView.frame.size.width = self.view.frame.width + 4
tabBArView.frame.origin.x = -2
extension UIImage {
class func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
let rect: CGRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}

Related

UIImage with resizableImageWithCapInsets Does Not Respond in Dark Mode

Does anyone know of a way to make a UIImage that has been stretched with resizableImageWithCapInsets respond to changes in light/dark mode? My current implementation only takes into consideration dark/light mode when it is being drawn the first time.
[thumbnailContainer addSubview:[self addTileBackgroundOfSize:thumbnailContainer.bounds]];
- (UIImageView *) addTileBackgroundOfSize:(CGRect)bounds {
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:bounds];
UIEdgeInsets insets = UIEdgeInsetsMake(10.0f, 49.0f, 49.0f, 10.0f);
UIImage *backgroundImage = [[UIImage imageNamed:#"UnivGalleryTile"] resizableImageWithCapInsets:insets];
backgroundView.image = backgroundImage;
return backgroundView;
}
I guess I could redraw them in a traitCollection delegate method but I was hoping there is a better way to make them respond.
First of all, there is no surprise here. When you say resizableImage, you make a new image. It is no longer the image you got from the asset catalog, so it has lost the automatic linkage / dynamism that makes an image change automatically to another image when the trait collection changes.
Second, that doesn't matter, because you can create that linkage with any two images (that are not in the asset catalog). You do that by way of the UIImageAsset class.
So here's a working example. Imagine that Faces is the name of a pair in the asset catalog, one for Any, one for Dark. I'll extract each member of the pair, apply resizable to each one, and then join the new pair together as variants of one another:
let tclight = UITraitCollection(userInterfaceStyle: .light)
let tcdark = UITraitCollection(userInterfaceStyle: .dark)
var smiley = UIImage(named: "Faces", in: nil, compatibleWith: tclight)!
var frowney = UIImage(named: "Faces", in: nil, compatibleWith: tcdark)!
let link = UIImageAsset()
let insets = UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 30)
smiley = smiley.resizableImage(withCapInsets: insets)
frowney = frowney.resizableImage(withCapInsets: insets)
link.register(smiley, with: tclight)
link.register(frowney, with: tcdark)
Or in Objective-C:
UITraitCollection* tclight = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
UITraitCollection* tcdark = [UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
UIImage* smiley = [UIImage imageNamed:#"Faces" inBundle:nil compatibleWithTraitCollection:tclight];
UIImage* frowney = [UIImage imageNamed:#"Faces" inBundle:nil compatibleWithTraitCollection:tcdark];
UIImageAsset* link = [UIImageAsset new];
UIEdgeInsets insets = UIEdgeInsetsMake(30, 30, 30, 30);
smiley = [smiley resizableImageWithCapInsets:insets];
frowney = [frowney resizableImageWithCapInsets:insets];
[link registerImage:smiley withTraitCollection:tclight];
[link registerImage:frowney withTraitCollection:tcdark];
All done. Notice that in the code there is no need to retain any of the objects (link, smiley, frowney).
Now if you insert one member of the pair into, say, an image view, it will change to the other automatically when the user light/dark mode changes:
let tc = self.traitCollection
let im = link.image(with: tc)
self.imageView.image = im
I'll switch back and forth between light and dark mode to prove that this is working:
I have solved it, but boy is this ugly. So, if anyone has a nicer solution I am open to it:
I first store the image view in an NSMutableArray:
- (UIImageView *) addTileBackgroundOfSize:(CGRect)bounds {
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:bounds];
UIEdgeInsets insets = UIEdgeInsetsMake(10.0f, 49.0f, 49.0f, 10.0f);
UIImage *backgroundImage = [[UIImage imageNamed:#"UnivGalleryTile"] resizableImageWithCapInsets:insets];
backgroundView.image = backgroundImage;
// Store image for re-drawing upon dark/light mode change
[thumbnailArray addObject:backgroundView];
return backgroundView;
}
And then I reset the background image manually when the user changes the screen mode:
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
for (int i = 0; thumbnailArray.count > i; i++) {
UIEdgeInsets insets = UIEdgeInsetsMake(10.0f, 49.0f, 49.0f, 10.0f);
UIImage *backgroundImage = [[UIImage imageNamed:#"UnivGalleryTile"] resizableImageWithCapInsets:insets];
((UIImageView *)[thumbnailArray objectAtIndex:i]).image = backgroundImage;
}
}
It seems resizableImageWithCapsInsets causes the image to lose its dynamic, auto-adapting properties. You could maybe try to create images for both appearances and put them together again into a dynamic image. Check out this gist on how this could be done.
In case of a .tiled image with .zero insets - there's a UIKit bug that removed the configuration, as it only checks for non-zero insets, and does not take into account a case of zero insets with tiled configuration.
A workaround is to do:
let responsiveZeroEdgeInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0000001)
let darkImage = yourDarkImage.resizableImage(withCapInsets: responsiveZeroEdgeInsets, resizingMode: .tile)
let lightImage = yourLightImage.resizableImage(withCapInsets: responsiveZeroEdgeInsets, resizingMode: .tile)
And then put them into the asset.
The trick is to use 0.0000001 insets.
I've opened a bug report with Apple: #9997202.

Cocoa ScreenSaverView

I am trying to play with ScreenSaverView in mac OS X.
I followed this tutorial http://cocoadevcentral.com/articles/000088.php
and it worked (can't say flawlessly ,but worked).
I also saw in part 2 of this tutorial they are playing with openGL stuff etc.
I have 2 general questions:
1)Can I use SprteKit within screensaver view?
2)What is wrong with my code here -> it compiles well, however I don't see anything except a black screen (I want to see my *.png).
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
[_imageView setImage:[NSImage imageNamed:#"SettingsButton.png"]];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
EDIT: Attempt to use a draw rect:
- (void)drawRect:(NSRect)rect0
{
[super drawRect:rect0];
NSImage *anotherImage = [NSImage imageNamed:#"SettingsButton.png"];
[anotherImage drawAtPoint:NSMakePoint(10,100) fromRect:NSMakeRect(0,0,[anotherImage size].width,[anotherImage size].height) operation:NSCompositeCopy fraction:1.0];
// Calculate a random color
CGFloat red = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat green = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
CGFloat blue = SSRandomFloatBetween( 0.0, 255.0 ) / 255.0;
NSColor* color = [NSColor colorWithCalibratedRed:red
green:green
blue:blue
alpha:1];
NSColor * color2 = [NSColor colorWithPatternImage:anotherImage];
[color2 set];
NSBezierPath * path = [NSBezierPath bezierPathWithRect:rect0];
[path fill];
}
When I set a color it works , I can see a screen filled by Random colors (fine as meant)
When I use color2 which is a pattern from the image
nothing is works :-( -> I tried different images
same nothing there...
I checked in build phase that I do copy the images as a bundle resources
What could be the problem ?
EDIT: Ok so after my attempt in drawRect I suspected that imageNamed method is causing a troubles and rewrite my origin attempt to:
- (instancetype)initWithFrame:(NSRect)frame isPreview:(BOOL)isPreview
{
self = [super initWithFrame:frame isPreview:isPreview];
if (self)
{
_imageView = [[NSImageView alloc]initWithFrame:[self bounds]];
NSBundle * tempBundle = [NSBundle bundleForClass:[self class]];
// Load your image, "top.tiff". Don't forget to release it when you are done (not shown).
NSImage * theImage = [[NSImage alloc] initWithContentsOfFile:
[tempBundle pathForImageResource:#"personal1.jpg"]];
[_imageView setImage:theImage];
[self addSubview:_imageView];
[self setAnimationTimeInterval:1.0];
}
return self;
}
And Whoaallaaa it worked !!(Kinda) I see my picture :-) !!!
As for part1 of the Question -> Yes it is possible
Just tried and it works !
with a little down side I can exit ScreenSaver only with cmd key xD
Managed to solve this issue,by subclassing SKView and delivering event to the next responder.
However it gave me a huge idea -> actually it just opens an opportunity to make a simple SK game as part of the screensaver could have been a cute feature.
Sadly I can't submit it to app store :)
It's not clear to me if a SpriteKit view (SKView) can even be used in a normal OSX/iOS app. I have searched in the past and found nothing.
If SpriteKit is anything like other game frameworks (from which it borrows much of its structure) then it will use a traditional game loop; i.e. clear screen, draw everything, wait a bit and then repeat.
Cocoa apps use an runloop that reacts to events and take steps to only redraw what needs to be redrawn.
So I would say "No" to your first question.
As far as your second, code-related, question is concerned, then I cannot see much wrong with it, however I am not familiar with that init method; what class does it subclass?

How to check if the Button Shapes setting is enabled?

iOS 7.1 includes a new Accessibility setting calls Button Shapes that causes some button text to be automatically underlined. Is there a way to detect this mode, or customize it for individual UIButtons?
(This to allow changing button labels such as a dash or underscore so that when underlined, they don't look like an equals sign, etc.)
As of iOS 14, you can use UIAccessibility.buttonShapesEnabled or UIAccessibilityButtonShapesEnabled(), which will be true when the setting is enabled.
Old question, but hopefully this helps someone. There's still no built-in method for checking if Button Shapes is enabled on iOS, so we added this:
#pragma mark - Accessibility
/**
* There's currently no built-in way to ascertain whether button shapes is enabled.
* But we want to manually add shapes to certain buttons when it is.
*/
static BOOL _accessibilityButtonShapesEnabled = NO;
+ (BOOL)accessibilityButtonShapesEnabled {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self checkIfButtonShapesEnabled];
});
return _accessibilityButtonShapesEnabled;
}
+ (void)checkIfButtonShapesEnabled {
UIButton *testButton = [[UIButton alloc] init];
[testButton setTitle:#"Button Shapes" forState:UIControlStateNormal];
_accessibilityButtonShapesEnabled = (BOOL)[(NSDictionary *)[testButton.titleLabel.attributedText attributesAtIndex:0 effectiveRange:nil] valueForKey:NSUnderlineStyleAttributeName];
}
Because there's also no notification if Button Shapes is disabled/enabled whilst the app is running, we run checkIfButtonShapesEnabled in applicationDidBecomeActive:, and push our own notification if the value has changed. This should work in all cases, because it is not currently possible to add the Button Shapes toggle to the "Accessibility Shortcut".
It looks like you can request the button label's attributes and test to see if it contains an NSUnderlineStyleAttributeName attribute. If you remove the NSUnderlineStyleAttributeName attribute the system will put it right back so it seems the trick is to explicitly set the label's underline attribute to 0. I've added the following to my custom button:
- (void) adjustLabelProperties // override underline attribute
{
NSMutableAttributedString *attributedText = [self.titleLabel.attributedText mutableCopy];
[attributedText addAttribute: NSUnderlineStyleAttributeName value: #(0) range: NSMakeRange(0, [attributedText length])];
self.titleLabel.attributedText = attributedText;
}
I know it's an old question but this code works. Tested in iOS 9.3
NSMutableAttributedString *attrStr = [btn.titleLabel.attributedText mutableCopy];
[attrStr enumerateAttributesInRange:NSMakeRange(0, [attrStr length])
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(NSDictionary *attributes, NSRange range, BOOL *stop) {
NSMutableDictionary *mutableAttributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
if([mutableAttributes objectForKey:NSUnderlineStyleAttributeName] != nil) {
//It's enabled for this button
}
}];
To disable button shapes for a specific button
[btn.titleLabel.attributedText addAttribute: NSUnderlineStyleAttributeName value: #(0) range: NSMakeRange(0, [attributedText length])];
I converted the code from this post to Swift (4.2):
import UIKit
public extension UIAccessibility {
public static var isButtonShapesEnabled: Bool {
let button = UIButton()
button.setTitle("Button Shapes", for: .normal)
return button.titleLabel?.attributedText?.attribute(NSAttributedString.Key.underlineStyle, at: 0, effectiveRange: nil) != nil
}
}
Usage:
if UIAccessibility.isButtonShapesEnabled {
// Apply button shapes style to custom button...
}
Tested and working in iOS 12.
Originally posted at my own question: Click
I had the same problem and I found no official solution. So the only workaround that I found until Apple releases a solution is to render a UIToolbar into an image and check if the button is underlined:
+ (BOOL)isUsesButtonShapes {
BOOL result = FALSE;
CGRect rect = CGRectMake(0, 0, 320, 44);
CGPoint point = CGPointMake(26, 33);
UIToolbar *toolbar = [[[UIToolbar alloc] initWithFrame:rect] autorelease];
toolbar.backgroundColor = [UIColor whiteColor];
toolbar.tintColor = [UIColor darkGrayColor];
toolbar.barTintColor = [UIColor whiteColor];
[toolbar setItems:#[[[[UIBarButtonItem alloc] initWithTitle:#"Test" style:UIBarButtonItemStyleBordered target:nil action:nil] autorelease]]];
toolbar.barStyle = UIBarStyleDefault;
toolbar.translucent = FALSE;
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[toolbar.layer renderInContext:context];
int bpr = CGBitmapContextGetBytesPerRow(context);
unsigned char *data = CGBitmapContextGetData(context);
if (data != NULL) {
int offset = (int) (bpr * point.y + 4 * point.x);
int blue = data[offset + 0];
result = blue < 250;
}
UIGraphicsEndImageContext();
return result;
}
It basically just renders the UIToolbar into an image:
Then it checks if there is an underline in the pixel under the "T". I know that this can easily break if Apple changes the way how the UIToolbar is rendered. But maybe this method can be improved and is at least better than nothing? Sorry, it isn't a good solution but I didn't find anything better yet.
This is only semi related, but I kind of "roll my own" for Button shapes and make the option available to the user via a settings menu.
I apologize for not being "spot on" regarding the question, but this is what I ended up with by thinking about the same question.
(The example is set up to always use a semi-circle for rounded corners regardless the size - please modify as you wish).
-(void)setBorderForButton:(UIButton*)theButton withSetting:(BOOL)theSetting{
if (theSetting == YES){
theButton.layer.cornerRadius = theButton.frame.size.height/2;
theButton.layer.borderWidth = 1;
theButton.layer.borderColor = [UIColor yourDesiredColor].CGColor;
theButton.clipsToBounds = YES;
}
}

Animating only the image in UIBarButtonItem

Ive seen this effect in 2 apps and I REALLY want to find how to do it.
The animation is in a UIBarButtonItem, and is only to the image. The image is a + symbol, and it rotates to a X.
If you want to see the effect you have to start a conversation with someone and next to the text input theres the + button for images and emoji's. Or heres a video of the effect in another app, after he taps the bar button you see it rotate to a X, http://www.youtube.com/watch?v=S8JW7euuNMo.
I have found out how to do the effect but only on a UIImageView, I have to turn off all the autoresizing and the view mode has to be centered, then apply the rotation transform to it. I have tried many ways of trying to have it work in a bar item and so far the best way is adding a image view instance, then setting it up and setting the view mode centered and autoresizing off and then using that image view for a custom bar item view. But when i do this, the effect works except while its doing it, the image will go off to the side a little bit instead of staying where it already is. Ive tried getting the center before the animation and set it during the animation but that doesnt do anything.
So the answer for this is you have to make a instance of the Image view, then set it up with no resizing and view mode is centered. Then add the image view to a UIButton with custom type, and then use the button as the custom view for the bar item.
- (IBAction)animate {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(45));
} completion:^(BOOL finished) {
imageView.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(0));
if ([imageView.image isEqual:[UIImage imageNamed:#"Add.png"]]) {
imageView.image = [UIImage imageNamed:#"Close.png"];
}
else imageView.image = [UIImage imageNamed:#"Add.png"];
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Add.png"]];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeCenter;
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0, 0, 40, 40);
[button addSubview:imageView];
[button addTarget:self action:#selector(animate) forControlEvents:UIControlEventTouchUpInside];
imageView.center = button.center;
barItem = [[UIBarButtonItem alloc] initWithCustomView:button];
navItem.rightBarButtonItem = barItem;
}
Recently had to do the same thing in Swift. I created a tutorial that includes starter and final projects, and goes step-by-step with some tips sprinkled in. The code looks like this:
#IBOutlet weak var rightBarButton: UIBarButtonItem! {
didSet {
let icon = UIImage(named: "star")
let iconSize = CGRect(origin: CGPointZero, size: icon!.size)
let iconButton = UIButton(frame: iconSize)
iconButton.setBackgroundImage(icon, forState: .Normal)
rightBarButton.customView = iconButton
rightBarButton.customView!.transform = CGAffineTransformMakeScale(0, 0)
UIView.animateWithDuration(1.0,
delay: 0.5,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 10,
options: .CurveLinear,
animations: {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
},
completion: nil
)
iconButton.addTarget(self, action: "tappedRightButton", forControlEvents: .TouchUpInside)
}
}
func tappedRightButton(){
rightBarButton.customView!.transform = CGAffineTransformMakeRotation(CGFloat(M_PI * 6/5))
UIView.animateWithDuration(1.0) {
self.rightBarButton.customView!.transform = CGAffineTransformIdentity
}
}
I wanted to keep the expanded tapping size that the native UIBarButtonItem view provides (such as -initWithBarButtonSystemItem:target:action: versus -initWithCustomView:).
Here's a basic implementation of my code.
- (void)setup {
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(navigationBarRightAction)];
}
- (void)navigationBarRightAction {
UIView *itemView = [self.navigationItem.rightBarButtonItem performSelector:#selector(view)];
UIImageView *imageView = [itemView.subviews firstObject];
if (self.shouldRotate) {
imageView.contentMode = UIViewContentModeCenter;
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.clipsToBounds = NO;
imageView.transform = CGAffineTransformMakeRotation(M_PI_4);
} else {
imageView.transform = CGAffineTransformIdentity;
}
}
You don't have to use a button as a custom view, it works in fact with less code using a UIImageView and adding a UITapGestureRecognizer.
I hope my solution below helps someone b/c I struggled with this for a long time until I got the bar button item to receive taps and get it to work with all the features I wanted. In my case, I made an "alert bell" bar button item that jingles when there are notifications, and then segues to a new tableview controller when tapped.
This was my solution (Swift 5):
#IBOutlet weak var notifyBell: UIBarButtonItem!
func updateNumNotesAndAnimateBell(_ numNotes: Int) {
guard let image = UIImage(named: "alertBellFill_\(numNotes)") else { return }
let imageView = UIImageView(image: image)
notifyBell.customView = imageView
notifyBell.customView?.contentMode = .center
let tap = UITapGestureRecognizer(target: self, action: #selector(notifyBellPressed))
notifyBell.customView?.addGestureRecognizer(tap)
let scaleTransformA = CGAffineTransform(scaleX: 0.8, y: 0.8)
let rotateTransformA = CGAffineTransform(rotationAngle: 0.0)
let hybridTransformA = scaleTransformA.concatenating(rotateTransformA)
let rotateTransformB = CGAffineTransform(rotationAngle: -1*CGFloat.pi*20.0/180.0)
let hybridTransformB = scaleTransformA.concatenating(rotateTransformB)
notifyBell.customView?.transform = hybridTransformA
UIView.animate(withDuration: 3,
delay: 1,
usingSpringWithDamping: 0.1,
initialSpringVelocity: 10,
options: [.allowUserInteraction, .curveEaseInOut],
animations: {
self.notifyBell.customView?.transform = numNotes > 0 ? hybridTransformB : scaleTransformA
},
completion: nil
)
}
#objc func notifyBellPressed(_ sender: UIBarButtonItem) {
performSegue(withIdentifier: "goToNotificationsTVC", sender: self)
}
Key discoveries for me were that:
-- .allowUserInteraction must be included in the animate options, otherwise the UIBarButtonItem won't be active until the animation completes.
-- You will likely have to declare YourBarButtonItem.customView?.contentMode = .center when using CGAffineTransform(rotationAngle: ) or else it will distort your image when it tries to rotate.
-- The code above includes a scale animation and rotate animation that is different depending on how many notifications I have. With zero notifications, the image is an empty bell, else, it displays the number of notifications in the bell image. I probably could've done this with an updating label, but I had already gone the route of making separate PNGs for each so this worked nicely.

Capturing full screenshot with status bar in iOS programmatically

I am using this code to capture a screenshot and to save it to the photo album.
-(void)TakeScreenshotAndSaveToPhotoAlbum
{
UIWindow *window = [UIApplication sharedApplication].keyWindow;
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)])
UIGraphicsBeginImageContextWithOptions(window.bounds.size, NO, [UIScreen mainScreen].scale);
else
UIGraphicsBeginImageContext(window.bounds.size);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
But the problem is whenever the screenshot is saved, I see the status bar of iPhone is not captured. Instead a white space appears at the bottom. Like the following image:
What am I doing wrong?
The status bar is actually in its own UIWindow, in your code you are only rendering the view of your viewcontroller which does not include this.
The "official" screenshot method was here but now seems to have been removed by Apple, probably due to it being obsolete.
Under iOS 7 there is now a new method on UIScreen for getting a view holding the contents of the entire screen:
- (UIView *)snapshotViewAfterScreenUpdates:(BOOL)afterUpdates
This will give you a view which you can then manipulate on screen for various visual effects.
If you want to draw the view hierarchy into a context, you need to iterate through the windows of the application ([[UIApplication sharedApplication] windows]) and call this method on each one:
- (BOOL)drawViewHierarchyInRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates
You may be able to combine the two above approaches and take the snapshot view, then use the above method on the snapshot to draw it.
The suggested "official" screenshot method doesn't capture status bar (it is not in the windows list of the application). As tested on iOS 5.
I believe, this is for security reasons, but there is no mention of it in the docs.
I suggest two options:
draw a stub status bar image from resources of your app (optionally update time indicator);
capture only your view, without status bar, or trim image afterwards (image size will differ from standard device resolution); status bar frame is known from corresponding property of application object.
Here is my code to take a screenshot and store it as NSData (inside an IBAction). With the sotred NSData then you can share or email or whatever want to do
CGSize imageSize = [[UIScreen mainScreen] bounds].size;
if (NULL != UIGraphicsBeginImageContextWithOptions)
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
else
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
// Iterate over every window from back to front
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![window respondsToSelector:#selector(screen)] || [window screen] == [UIScreen mainScreen])
{
// -renderInContext: renders in the coordinate space of the layer,
// so we must first apply the layer's geometry to the graphics context
CGContextSaveGState(context);
// Center the context around the window's anchor point
CGContextTranslateCTM(context, [window center].x, [window center].y);
// Apply the window's transform about the anchor point
CGContextConcatCTM(context, [window transform]);
// Offset by the portion of the bounds left of and above the anchor point
CGContextTranslateCTM(context,
-[window bounds].size.width * [[window layer] anchorPoint].x,
-[window bounds].size.height * [[window layer] anchorPoint].y);
// Render the layer hierarchy to the current context
[[window layer] renderInContext:context];
// Restore the context
CGContextRestoreGState(context);
}
}
// Retrieve the screenshot image
UIImage *imageForEmail = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSData *imageDataForEmail = UIImageJPEGRepresentation(imageForEmail, 1.0);
Answer of above question for Objective-C is already write there, here is the Swift version answer of above question.
For Swift 3+
Take screenshot and then use it to display somewhere or to send over web.
extension UIImage {
class var screenShot: UIImage? {
let imageSize = UIScreen.main.bounds.size as CGSize;
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
guard let context = UIGraphicsGetCurrentContext() else {return nil}
for obj : AnyObject in UIApplication.shared.windows {
if let window = obj as? UIWindow {
if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
// so we must first apply the layer's geometry to the graphics context
context.saveGState();
// Center the context around the window's anchor point
context.translateBy(x: window.center.x, y: window.center
.y);
// Apply the window's transform about the anchor point
context.concatenate(window.transform);
// Offset by the portion of the bounds left of and above the anchor point
context.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
y: -window.bounds.size.height * window.layer.anchorPoint.y);
// Render the layer hierarchy to the current context
window.layer.render(in: context)
// Restore the context
context.restoreGState();
}
}
}
guard let image = UIGraphicsGetImageFromCurrentImageContext() else {return nil}
return image
}
}
Usage of above screenshot
Lets display above screen shot on UIImageView
yourImageView = UIImage.screenShot
Get image Data to save/send over web
if let img = UIImage.screenShot {
if let data = UIImagePNGRepresentation(img) {
//send this data over web or store it anywhere
}
}
Swift, iOS 13:
The code below (and other ways of accessing) will now crash the app with a message:
App called -statusBar or -statusBarWindow on UIApplication: this code must be changed as there's no longer a status bar or status bar window. Use the statusBarManager object on the window scene instead.
The window scenes and statusBarManager's really only give us access to frame - if this is still possible, I am not aware how.
Swift, iOS10-12:
The following works for me, and after profiling all the methods for capturing programmatic screenshots - this is the quickest, and the recommended way from Apple following iOS 10
let screenshotSize = CGSize(width: UIScreen.main.bounds.width * 0.6, height: UIScreen.main.bounds.height * 0.6)
let renderer = UIGraphicsImageRenderer(size: screenshotSize)
let statusBar = UIApplication.shared.value(forKey: "statusBarWindow") as? UIWindow
let screenshot = renderer.image { _ in
UIApplication.shared.keyWindow?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
statusBar?.drawHierarchy(in: CGRect(origin: .zero, size: screenshotSize), afterScreenUpdates: true)
}
You don't have to scale your screenshot size down (you can use UIScreen.main.bounds directly if you want)
Capture the full screen of iPhone, get the status bar by using KVC:
if let snapView = window.snapshotView(afterScreenUpdates: false) {
if let statusBarSnapView = (UIApplication.shared.value(forKey: "statusBar") as? UIView)?.snapshotView(afterScreenUpdates: false) {
snapView.addSubview(statusBarSnapView)
}
UIGraphicsBeginImageContextWithOptions(snapView.bounds.size, true, 0)
snapView.drawHierarchy(in: snapView.bounds, afterScreenUpdates: true)
let snapImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
The following works for me, capturing the status bar fine (iOS 9, Swift)
let screen = UIScreen.mainScreen()
let snapshotView = screen.snapshotViewAfterScreenUpdates(true)
UIGraphicsBeginImageContextWithOptions(snapshotView.bounds.size, true, 0)
snapshotView.drawViewHierarchyInRect(snapshotView.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()