UIImage with resizableImageWithCapInsets Does Not Respond in Dark Mode - objective-c

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.

Related

IOS Tab Bar height issue on iPhone X

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
}

UiStepper IOS7 Remove Border it's possible?

does anyone know if you can remove the edge of the UIStepper of Ios7? I wish you could see only the decrease and the increase without a border around ... E 'possible to make a custom?
thanks to all
If you have a custom image for the increment and decrement buttons you can use this solution. Make the tintColor property of the stepper [UIColor clearColor]. That way the border and the buttons will be invisible. To prevent the buttons being invisible, do this:
UIImage *incrementImageFromFile = [UIImage imageNamed:#"theName"];
UIImage *incrementImage = [incrementImageFromFile imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
You also need to make your images the same tint color as you would like to use.
Actually yes, you can set the background image to a pure color image, like this:
- (UIImage *)emptyImageForSteper
{
UIGraphicsBeginImageContext(CGSizeMake(1, 1));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, COLOR_BUTTON_LIGHT.CGColor);
CGContextFillRect(context, CGRectMake(0, 0, 1, 1));
UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
[steper setBackgroundImage:self.emptyImageForSteper forState:UIControlStateNormal];
there you go
It works,for Swift coders:
var incrementImageFromFile : UIImage = UIImage(named: "Offer_Plus")!
var incrementImage : UIImage = incrementImageFromFile.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
var decrementImageFromFile : UIImage = UIImage(named: "Offer_Minus")!
var decrementImage : UIImage = decrementImageFromFile.imageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal)
self.seatStepper.tintColor = UIColor.clearColor()
self.seatStepper.setDecrementImage(decrementImage, forState: UIControlState.Normal)
self.seatStepper.setIncrementImage(incrementImage, forState: UIControlState.Normal)
OutPut is:
It's not possible to customise the UIStepper. You can only change the background image, the tintColor, and the two images (plus and minus).
I think the best and easy way is to create your own like this.
1) Create two buttons and put as text + and -
2) This is the starting code. Then you need to handle the maximum and minimum values:
.h file
#interface ViewController : UIViewController {
NSInteger value;
}
- (IBAction)plus:(id)sender;
- (IBAction)minus:(id)sender;
.m file
- (IBAction)plus:(id)sender {
value++;
}
- (IBAction)minus:(id)sender {
--value;
}

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.

How can I make an UIImage programmatically?

This isn't what you probably thought it was to begin with. I know how to use UIImage's, but I now need to know how to create a "blank" UIImage using:
CGRect screenRect = [self.view bounds];
Well, those dimensions. Anyway, I want to know how I can create a UIImage with those dimensions colored all white. No actual images here.
Is this even possible? I am sure it is, but maybe I am wrong.
Edit
This needs to be a "white" image. Not a blank one. :)
You need to use CoreGraphics, as follows.
CGSize size = CGSizeMake(desiredWidth, desiredHeight);
UIGraphicsBeginImageContextWithOptions(size, YES, 0);
[[UIColor whiteColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code creates a new CoreGraphics image context with the options passed as parameters; size, opaqueness, and scale. By passing 0 for scale, iOS automatically chooses the appropriate value for the current device.
Then, the context fill colour is set to [UIColor whiteColor]. Immediately, the canvas is then actually filled with that color, by using UIRectFill() and passing a rectangle which fills the canvas.
A UIImage is then created of the current context, and the context is closed. Therefore, the image variable contains a UIImage of the desired size, filled white.
Swift version:
extension UIImage {
static func emptyImage(with size: CGSize) -> UIImage? {
UIGraphicsBeginImageContext(size)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
If you want to draw just an empty image, you could use UIKit UIImageBeginImageContextWithOptions: method.
UIGraphicsBeginImageContext(CGSizeMake(width, height));
CGContextAddRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, width, height)); // this may not be necessary
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
The code above assumes that you draw a image with a size of width x height. It adds rectangle into the graphics context, but it may not be necessary. Try it yourself. This is the way to go. :)
Or, if you want to create a snapshot of your current view you would type code like;
UIGraphicsBeginImageContext(CGSizeMake(self.view.size.width, self.view.size.height));
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Don't forget to include Quartz library if you use layer.
Based on #HixField and #Rudolf Adamkovič answer. here's an extension which returns an optional, which I believe is the correct way to do this (correct me if I'm wrong!)?
This extension allows you to create a an empty UIImage of what ever size you need (up to memory limit) with what ever fill color you want, which defaults to white, if you want the image to be the clear color you would use something like the following:
let size = CGSize(width: 32.0, height: 32.0)
if var image = UIImage.imageWithSize(size:size, UIColor.clear) {
//image was successfully created, do additional stuff with it here.
}
This is for swift 3.x:
extension UIImage {
static func imageWithSize(size : CGSize, color : UIColor = UIColor.white) -> UIImage? {
var image:UIImage? = nil
UIGraphicsBeginImageContext(size)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(color.cgColor)
context.addRect(CGRect(origin: CGPoint.zero, size: size));
context.drawPath(using: .fill)
image = UIGraphicsGetImageFromCurrentImageContext();
}
UIGraphicsEndImageContext()
return image
}
}
Using the latest UIGraphics classes, and in swift, this looks like this (note the CGContextDrawPath that is missing in the answer from user1834305, this is the reason that is produces an transparant image) :
static func imageWithSize(size : CGSize, color : UIColor) -> UIImage {
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
CGContextSetFillColorWithColor(context, color.CGColor)
CGContextAddRect(context, CGRect(x: 0, y: 0, width: size.width, height: size.height));
CGContextDrawPath(context, .Fill)
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image
}
Same solution in C# for Xamarin.iOS :
UIGraphics.BeginImageContext(new CGSize(width, height));
var image = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();

Cocoa Touch - Adding texture with overlay view

I have a set of tiles as UIViews that have a programmable background color, and each one
can be a different color. I want to add texture, like a side-lit bevel, to each one. Can this be done with an overlay view or by some other method?
I'm looking for suggestions that don't require a custom image file for each case.
This may help someone, although this was pieced together from other topics on SO.
To create a beveled tile image with an arbitrary color for normal and for retina display, I made a beveled image in photoshop and set the saturation to zero, making a grayscale image called tileBevel.png
I also created one for the retina display (tileBevel#2x.png)
Here is the code:
+ (UIImage*) createTileWithColor:(UIColor*)tileColor {
int pixelsHigh = 44;
int pixelsWide = 46;
UIImage *bottomImage;
if([UIScreen respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2.0) {
pixelsHigh *= 2;
pixelsWide *= 2;
bottomImage = [UIImage imageNamed:#"tileBevel#2x.png"];
}
else {
bottomImage = [UIImage imageNamed:#"tileBevel.png"];
}
CGImageRef theCGImage = NULL;
CGContextRef tileBitmapContext = NULL;
CGRect rectangle = CGRectMake(0,0,pixelsWide,pixelsHigh);
UIGraphicsBeginImageContext(rectangle.size);
[bottomImage drawInRect:rectangle];
tileBitmapContext = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(tileBitmapContext, kCGBlendModeOverlay);
CGContextSetFillColorWithColor(tileBitmapContext, tileColor.CGColor);
CGContextFillRect(tileBitmapContext, rectangle);
theCGImage=CGBitmapContextCreateImage(tileBitmapContext);
UIGraphicsEndImageContext();
return [UIImage imageWithCGImage:theCGImage];
}
This checks to see if the retina display is used, sizes the rectangle to draw in, picks the appropriate grayscale base image, set the blending mode to overlay, then draws a rectangle on top of the bottom image. All of this is done inside a graphics context bracketed by the BeginImageContext and EndImageContext calls. These set the current context needed by the UIImage drawRect: method. The Core Graphics functions need the context as a parameter, which is obtained by a call to get the current context.
And the result looks like this:
If you want to preserve the alpha channel of the source image, just add this to jim's code before the fill rect:
// Apply mask
CGContextTranslateCTM(tileBitmapContext, 0, rectangle.size.height);
CGContextScaleCTM(tileBitmapContext, 1.0f, -1.0f);
CGContextClipToMask(tileBitmapContext, rectangle, bottomImage.CGImage);
Swift 3 solution, essentially based on Jim's answer with Scriptease's addition, and some minor changes:
class func image(bottomImage: UIImage, topImage: UIImage, tileColor: UIColor) -> UIImage? {
let pixelsHigh: CGFloat = bottomImage.size.height
let pixelsWide: CGFloat = bottomImage.size.width
let rectangle = CGRect.init(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
UIGraphicsBeginImageContext(rectangle.size);
bottomImage.draw(in: rectangle)
if let tileBitmapContext = UIGraphicsGetCurrentContext() {
tileBitmapContext.setBlendMode(.overlay)
tileBitmapContext.setFillColor(tileColor.cgColor)
tileBitmapContext.scaleBy(x: 1.0, y: -1.0)
tileBitmapContext.clip(to: rectangle, mask: bottomImage.cgImage!)
tileBitmapContext.fill(rectangle)
let theCGImage = tileBitmapContext.makeImage()
UIGraphicsEndImageContext();
if let theImage = theCGImage {
return UIImage.init(cgImage: theImage)
}
}
return nil
}