Get height of a hidden, dynamically populated UIView - ios

I am trying to get a "screenshot" of a specific UIView that gets loaded from a xib that is never to be seen by the user.
Here's the code:
let nibViews = NSBundle.mainBundle().loadNibNamed("CardView", owner: ownedBy, options: nil)
if let cardView = nibViews.first as? CardView {
UIGraphicsBeginImageContextWithOptions(cardView.bounds.size, false, UIScreen.mainScreen().scale)
cardView.drawViewHierarchyInRect(cardView.bounds, afterScreenUpdates: true)
let imageAgain = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
//do something with image
The issue I'm having is that the cardView bounds, when I pass them into the UIGraphicsBeginImageContextWithOptions, are not correct. Specifically the height is not correct. If i println(cardView.bounds.size.height) before UIGraphicsBeginImageContextWithOptions, I get a generic "600" for the height... which distorts the image that gets created.
I did find a "solution", but not something I'm feeling comfortable with - if I call the UIGraphicsBeginImageContextWithOptions code block TWICE, on the second pass, the cardView.bounds.size.height returns the CORRECT height based on the content added to the cardView (also taking into account the constraints on the xib)
What seems to be happening is, the cardView's true layout isn't fully realized until drawViewHierarchyInRect is called once.
My question is - what is drawViewHierarchyInRect doing to make cardView report the correct height? And can I somehow do that thing through some separate function without creating 2 screenshots of the UIView?

Related

UITableViewCell Transform Breaks in iOS 11

In an app I've built, I had a table view showing an image to the left of each cell by running the following code inside of cellForRowAtIndexPath:
cell.imageView.image = [UIImage imageNamed:myImage];
But the image was too big, so I shrunk it:
cell.imageView.transform = CGAffineTransformMakeScale(0.3, 0.3);
This worked just fine in iOS 10. But once I upgraded to the newest Xcode with the iOS 11 SDK, the images got enormous. It turns out that that second line of code transforming the image view is now doing nothing: I can comment it out, change the 0.3's to something else, etc., and it doesn't make any difference. CGAffineTransformMakeScale still has documentation in the new Xcode, so I'm assuming it wasn't deprecated, but then why did this break with iOS 11, and how do I fix it? Any ideas? Thanks in advance! Please note, I'm using Objective-C.
Edit:
Just tried 3 changes to the code:
Change the second line to cell.imageView.transform = CGAffineTransformMakeScale(0.0000001, 0.0000001);. Nothing happens (i.e., the image views and the images inside them are still just as huge).
Change the second line to cell.imageView.transform = CGAffineTransformMakeScale(0, 0);. The image disappears from the image view, but the image view is still the same size, and you can tell because it still displaces the text in the cell and pushes it far to the right.
Remove the first line of code (no longer assigning an image to the imageview). The imageview disappears, and the text moves all the way to the left of the cell.
Perhaps this can help shed some light on what's going on?
So if you are trying to adjust the image size to fit the imageView you should actually use the imageView's contentMode property like so:
cell.imageView.contentMode = UIViewContentModeScaleAspectFit;
or in Swift for others
cell.imageView.contentMode = .scaleAspectFit
This keeps the dimensions of the image and fits the maximum size image onto the imageView without changing the dimensions
You could also try UIViewContentModeScaleAspectFit (or .scaleAspectFill in Swift) which basically fills the dimensions of the imageView entirely, but if the picture is wider or taller than the image view it crops what can't fit.
Here are all the contentModes directly from a Obj-C and Swift source files:
typedef NS_ENUM(NSInteger, UIViewContentMode) {
UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit, // contents scaled to fit with fixed aspect. remainder is transparent
UIViewContentModeScaleAspectFill, // contents scaled to fill with fixed aspect. some portion of content may be clipped.
UIViewContentModeRedraw, // redraw on bounds change (calls -setNeedsDisplay)
UIViewContentModeCenter, // contents remain same size. positioned adjusted.
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
};
public enum UIViewContentMode : Int {
case scaleToFill
case scaleAspectFit // contents scaled to fit with fixed aspect. remainder is transparent
case scaleAspectFill // contents scaled to fill with fixed aspect. some portion of content may be clipped.
case redraw // redraw on bounds change (calls -setNeedsDisplay)
case center // contents remain same size. positioned adjusted.
case top
case bottom
case left
case right
case topLeft
case topRight
case bottomLeft
case bottomRight
}
EDIT:
Since I see you're also interested in changing the dimensions of the imageView itself ("to leave more room for the text") what I would suggest is actually, either in storyboard or programmatically use AutoLayout to add your own imageView and have it sized and placed how you want it, instead of using the default imageView on a cell which is meant as a convenient/standardized tool.
If you are unfamiliar this I would google for an AutoLayout tutorial. Or maybe "using AutoLayout to create custom UITableViewCell"
If you dont actually want to create your own subclass you can try setting the `cell.imageView.frame = ..." somewhere to manually resize it to what you want, then setting its content mode to make sure the image fits nicely still.
See this question: How do I make UITableViewCell's ImageView a fixed size even when the image is smaller
Found an answer to my own question, with credit due to Paul's answer from this question: How to resize a cell.imageView in a TableView and apply tintColor in Swift
CGSize itemSize = CGSizeMake(50, 50);
UIGraphicsBeginImageContextWithOptions(itemSize, false, 0);
CGRect imageRect = CGRectMake(0, 0, itemSize.width, itemSize.height);
[cell.imageView.image drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
I still don't know why the old CGAffineTransformMakeScale doesn't work anymore, but this gets the job done.

Memory issue decodeObjectForKey (Swift project)

Within an Swift application exporting the individual single viewController images (using the drawViewHierarchyInRect).
For each viewController I recover the contents of imageViews as follows:
self.imageView = (aDecoder.decodeObjectForKey("imageLevel") as? UIImageView)
self.imageView!.contentMode = UIViewContentMode.ScaleAspectFit
self.imageView!.multipleTouchEnabled = true
self.imageView!.autoresizesSubviews = true
self.addSubview(imageView!)
After 15 "page": crash. If I comment these lines, the code works again. Is there an alternative to decodeObjectForKey?
I'm not really sure what you're doing here, but you shouldn't be trying to encode a UIImageView and save it off as the view is tied to other items. You could encode the UIImage instead. Your UIImageView should come from your storyboard/xib and then you set the image you decoded to the UIImageView.

iOS: Solid Border Outside UIView

A layer's borderWidth and borderColor properties draw a border inside the view. Remykits pointed this out here.
A layer's shadow... properties cannot be used to create a border that both appears on all four sides and is opaque for reasons I showed here.
What I failed to specify in that question (and the reason I've opened a new one) is that I want the border to be outside the view. Increasing the frame of the view to compensate for the space lost, as has been suggested, doesn't work; I'm using a UIImageView, so even if the frame is increased, the image is still cropped.
Another suggestion was to change the contentMode of the UIImageView to .Center, in combination with changing the size of the view, but this doesn't work as the view then isn't the proper size.
The solution I first thought of was to create another UIView "behind" this UIImageView, and give it a backgroundColor to mimic the effect of a border. I also thought of creating a custom subclass of UImageView. Both courses of action, however, involve making calculations based on the frame of the view. I've had many problems with the frame not being set by AutoLayout at the proper time, etc.
Other things that come to mind are digitally adding a border to the image or positioning the image in a specific part of the UIImageView. (My attempt at the latter was imageView.layer.contentsRect = CGRectInset(imageView.bounds, 4, 4), which resulted in a strangely pixellated image.)
To be clear, what I'm looking for is this:
It really feels like there should be a simpler way to do this than creating a new class or view. Any help appreciated.
Aha! Stitching together aykutt's comment about resizing the image and changing the conentMode, Paul Lynch's answer about resizing images, and rene's (life-saving) answer about what to do your subviews actually aren't laid out in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.myContainer.setNeedsLayout()
self.myContainer.layoutIfNeeded()
var width: CGFloat = 4 //the same width used for the border of the imageView
var rect = CGRectInset(imageView.bounds, width, width)
var size = CGSizeMake(rect.width, rect.height)
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
image.drawInRect(CGRectMake(0, 0, size.width, size.height))
var new = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.imageView.contentMode = .Center
self.imageView.image = new
}

Get an image from two imageViews in Swift

I'm developing an app where I have an area where there is an image in the background, and another image that I can move, like a sticker.
My goal is to create and save an image with the background image and the "sticker" above, using Swift. Here's my function that allows me to do what I want (my background view is called "imageView", my imageview sticker is called "jacques") :
let newSize = CGSizeMake(imageView.image!.size.width, imageView.image!.size.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0);
imageView.image!.drawInRect(CGRectMake(0,0,newSize.width,newSize.height))
let jacquesX = ((jacques.frame.origin.x - imageView.frame.origin.x) * (imageView.image?.size.width)!) / UIScreen.mainScreen().bounds.width
let jacquesY = ((jacques.frame.origin.y - imageView.frame.origin.y) * (imageView.image?.size.height)!) / UIScreen.mainScreen().bounds.height
let jacquesWidth: CGFloat = jacques.image!.size.width
let jacquesHeight: CGFloat = jacques.image!.size.height
jacques.image!.drawInRect(CGRectMake(jacquesX, jacquesY, jacquesWidth, jacquesHeight))
let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(newImage, self, "image:didFinishSavingWithError:contextInfo:", nil)
Unfortunately, my sticker isn't the right size. I think it's well placed in the image, but it's size is way too small. I don't know if my solution is the best one, i'm open to all suggestion. And I'm new, so if you have good practice to share, i'm all ears :)
You could do this in the storyboard. First you could size the picture however you want. To create a sticker effect you could just add imageView, size it to background size,and then put jacques on the storyboard, and finally resize them.

Merge two imageViews into one and save iOS swift

I have 2 imageViews like below. (ScrollView has subview of imageView)
i want to take the image of each one and merged to one.
i tried using taking screenshot and crop. but when it comes to different iphone screensizes and resolutions it doesn't work well.
can any one guide me how to do this?
Why not just add them both to one UIView?
It is also possible to add a subview to your image view and extend the frame.
[secondImageView setFrame:CGRectMake(x,y + newImageHeight,width,height)];
[self.myImageView setFrame:CGRectMake(x,y,width,height + newImageHeight)];
[self.myImageView addSubview:secondImageView];
I think your way is right, also you can solve scale size problem very easily with this method.
func mergeScreenshot() {
let layer = UIApplication.sharedApplication().keyWindow.layer
let scale = UIScreen.mainScreen().scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale); // reconsider size property for your screenshot
layer.renderInContext(UIGraphicsGetCurrentContext())
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}
But you should edit sizes again. For example if you're using autolayout you can create IBOutlet and than use it instead of layer.frame.size property which I used.
Hope it helps.

Resources