How to get square cropped photo thumbnail in ios using swift - ios

I want to get a photo thumbnail for my collection view cell, this is how i did it:
imageManager.requestImageForAsset(asset as! PHAsset, targetSize:CGSizeMake(80, 80), contentMode: .AspectFit, options: nil, resultHandler: {(result, info)->Void in
cell.setThumbnail(result!)
}
})
This will get a thumbnail that is stretched, I want a square cropped thumbnail that keeps the photo's original ratio like iPhone's Photo app did.
Any ideas? Please respond in swift, because I am new to ios programming, and I start with swift, thanks.

If you are working with Interface Builder where you can set the properties on your UIImageView, take a look at these two properties:
The view mode will affect whether it looks stretched or if it fits. Sometimes, though, even though it technically fits, it may still overflow the bounds of the cell rect if your image is too big. In that case, you can enable "Clip Subviews" which will make sure the image does not display outside the bounds of the image view rect.
If you are not using Interface Builder, both of these properties can be set on the cell in code instead, i.e.:
// This is assuming your UIImageView inside your cell is
// called 'thumbnail'. You could put this code inside of your
// .setThumbnail() function instead--though it's not clear
// what all that is doing from you code snippet.
cell.thumbnail.clipsToBounds = true
cell.thumbnail.contentMode = .ScaleAspectFill
The various content modes you can choose from are defined like this in UIKit:
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
}

Related

Setting Image To Nil Causes ImageView to Disappear in Stack View

I have recently moved a bunch of UIImageViews into nested Stack Views (Horizontal and Vertical). Whenever the user presses the play button it animates, then starts a timer. Once the timer reaches 0 it is supposed to flip each card consecutively and hide the image itself. This was working until I added them to a Stack View. I've read that the stack views are buggy when it comes to this sort of thing, but any attempt at fixing it hasn't worked thus far. I'm at an impasse, can anyone point me in the right direction?
Issue Occurs In Here I Think
//INFO: Disable cards while viewing.
for card in cardsPhone
{
card.isUserInteractionEnabled = false
flipCard(sender: card)
}
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 5) {
for card in self.cardsPhone
{
DispatchQueue.main.async {
//THIS IS THE ANIMATION CALL THAT MAKES VIEWS DISAPPEAR.
self.flipCard(sender: card)
}
usleep(20000)
}
DispatchQueue.main.async{
//INFO: Enable cards after hidden.
for card in self.cardsPhone
{
card.isUserInteractionEnabled = true
}
//INFO: Enable play button after cards are hidden to prevent crashing layout.
self.playButtonView.isUserInteractionEnabled = true
}
}
Flip Animation Controller
func flipCard(sender: UIImageView)
{
playButtonView.isUserInteractionEnabled = false
//INFO: If there is no UIImage, assign the appropriate image from the array.
if sender.image == nil
{
sender.image = cardsImages[sender.tag]
UIView.transition(with: sender, duration: 0.3, options: .transitionFlipFromLeft, animations: nil, completion: nil)
}
//INFO: If there is an image, remove it and replace with no Image.
else
{
sender.image = nil
UIView.transition(with: sender, duration: 0.3, options: .transitionFlipFromRight, animations: nil, completion: nil)
}
}
Update
I've discovered that if I replace the image on the UIImageView then that is actually what is causing it to disappear. So the issue is related to the code above in the else statement where sender.image = UIImage(). It doesn't matter if I replace with a different image or not, the moment the image is changed it disappears.
I've read that the stack views are buggy when it comes to this sort of thing
Well, that's not so. They are in fact quite sophisticated and consistent, and their behavior is well defined.
What is a stack view? It's a builder of autolayout constraints. And that is all it is. Putting views inside a stack view means "Please configure these views with equal spacing" (or whatever your setting is for the stack view) "using autolayout constraints that you impose."
Okay, but how does the stack view do that? Autolayout requires four pieces of information, x and y position, and width and height. The stack view will supply the position, but what about the size (width and height) of its views? Well, one way to tell it is to give your views internal constraints (if they don't conflict with what else the stack view will do). But in the absence of that, it has to use the intrinsic size of its views. And the intrinsic size of an image view under autolayout is the size of the image it contains.
So everything was fine when you had image views with images that were the same size. They all had the same intrinsic size and now the stack view could space them out. But then you took away an image view's image. Now it has no image. So it has no intrinsic size. So the stack view just removes it from the row (or column or whatever it is).
Your workaround is actually quite a good one, namely, to make sure that the image view always has an image; if it is to look blank, you just give it a blank image. The image looks like the background, but it is still an image with an actual size, and so it gives the image view a size and the image view continues to hold its place.
However another solution would have been simply to give the image view a width constraint the same size as the card image's width.
Solution
I've discovered that changing an image on a UIImageView while it's inside of a stack causes it to completely disappear. This is a weird Xcode but but the solution I found was to create a custom background on the fly to get rid of the image and only have the background color. This was done with the following code snippet. It's a bit messy, but it gets the job done.
func setColorImage(viewToModify: UIImageView, colorToSet: UIColor)
{
let rect = CGRect(origin: .zero, size: viewToModify.layer.bounds.size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
colorToSet.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard (image?.cgImage) != nil else { return }
viewToModify.image = image
viewToModify.backgroundColor = colorToSet
viewToModify.isUserInteractionEnabled = false
}
Essentially this code creates a new cgRect, sets the color, creates a new image from that color, then applies it back to the UIImageView.

Self-sizing UICollectionView cells with async image loading

I have a horizontal UICollectionView in which I display images that are loaded asynchronously. The cells are supposed to have their width fit the image.
In viewDidLoad of my view controller, I set the estimated cell size:
(collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.estimatedItemSize = CGSize(width: 400, height: 400)
In cellForItem, I start the download task using Kingfisher:
cell.imageView.kf.setImage(with: url) { (_, _, _, _) in
cell.layoutSubviews()
}
Inside my cell, I have the following code inside the layoutSubviews method:
// 326 is the image view's height
// .aspectRatio is a custom extension that returns: size.width / size.height
imageViewWidthConstraint.constant = 326 * image.aspectRatio
layoutIfNeeded()
In the storyboard, I have properly setup the layout constraints so that imageViewWidthConstraint is respected for the cell's width.
The following is the result when running my app:
As you can see, the cells have a width of 400, although the image was loaded and the layout updated. And, as result, the images are stretched to fill the image view.
When I scroll to the right & then back, the cells are removed from the collection view and loaded back in, and are now properly laid out:
While scrolling, the cells adjust their width, sometimes to the correct width, sometimes to the wrong one.
What am I doing wrong here?
Since your images come in asynchronously it may take some time to be loaded. Only once they are loaded you can actually know the size or ratio of the image. That means for every image that is loaded you need to "reload" your layout. From a short search this looks promising.
At least this way you may get some animations, otherwise your cells will just keep jumping when images start to be loaded.
In any case I would advise you to avoid this. If I may assume; you are getting some data from server from which you use delivered URLs to download the images and show them on the collection view. The best approach (if possible) is to request that the API is extended so that you receive dimensions of images as well. So instead of
{
id: 1,
image: "https://..."
}
You could have
{
id: 1,
image: {
url: "https://...",
width: 100,
height: 100
}
}
You can now use these values to generate aspect ratio width/height before you download the images.
Next to that I don't really see any good solution for the whole thing to look nice (without jumping around).

UIScrollView with Horizontal Zoom - Bezier Paths Drawn within Subviews Are Blurry

I have a scroll view that zooms only horizontally on pinch (based on this answer)
The content view has several children that are evenly spaced horizontally, and placed at different heights. Basically, I'm plotting a graph with dots.
Each marker is a custom UIView subclass that has a label as subview and draws a red circle inside drawRect(_:).
(The scroll view and its only child the content view use Autolayout, but all subviews of the content view are placed with frames calculated at runtime; no constraints except the label positioning respect to the marker)
I have modified the answer linked above, so that -when zooming- the dots get more spaced horizontally, but stay the same size.
This is part of the code for my content view:
override var transform: CGAffineTransform {
get {
return super.transform
}
set {
if let unzoomedViewHeight = unzoomedViewHeight {
// ^Initial height of the content view, captured
// on layoutSubviews() and used to calculate a
// a zoom transform that preserves the height
// (see linked answer for details)
// 1. Ignore vertical zooming for this view:
var modified = newValue
modified.d = 1.0
modified.ty = (1.0 - modified.a) * unzoomedViewHeight/2
super.transform = modified
// 2. Ignore zooming altogether in marker subviews:
var markerTransform = CGAffineTransformInvert(newValue)
markerTransform.d = 1.0 // No y-scale
for subview in subviews where subview is MarkerView {
subview.transform = markerTransform
}
}
}
}
Next, I want to connect my dots with straight line segments. The problem is, when I zoom the scroll view the segments become blurry.
In addition to the blurring, and because I am only zooming horizontally, the segments "shear" a bit (depending on their their slope), and thus the line width becomes uneven:
(Right now I am placing an intermediate view between each pair of dots, that draws a single segment - but the result is the same with a single path)
I have tried making the segment-drawing views aware of the transform that is applied to them by their parent, revert it, and instead modify their frame accordingly (and redraw the path within the new bounds); however it doesn't seem to work.
What is the best way to draw zoom-resistant bezier paths inside a scroll view?
Update:
I got rid of the pixelation by following this answer to a similar question (almost duplicate?); in my case the code translates to:
func scrollViewDidEndZooming(scrollView: UIScrollView, withView view: UIView?, atScale scale: CGFloat)
{
for segment in contentView.subviews where segment is SegmentView {
segment.layer.contentsScale = scale
segment.setNeedsDisplay()
}
}
...However, I still have the uneven line width issue due to stretching the (diagonal) segments only in the horizontal direction.
My initial thought, having not done this before exactly, is to store the graph as a bezier path rather than as a set of view line and point views, and based on a single unit axis. You could convert your data into this form just for drawing.
So, all points on the bezier path are normalised into the range 0 to 1.
Once you've done that you can apply a transform to the bezier path to translate (move) and scale (zoom) to the part you want and then draw the bezier path at full resolution.
This will work and is different to your current situation because your current code draws the views and then scales then so you get artifacts. The above option scales the path and then draws it.

How add background image to UIControl

I create 3rd party keyboard and i try add background to my keys. I use UIControl for my keys, without storyboard.
I have several types of buttons and try to add background UIView for my characters.
let img = UIImage(named:"KeyBackground")
var bgImage: UIImageView?
bgImage = UIImageView(image: img)
bgImage!.frame = keyboardKey.frame
keyboardKey.addSubview(bgImage!)
keyboardKey.sendSubviewToBack(bgImage!)
keyboardKey.backgroundColor = UIColor.clearColor()
But is don't work.
I try to add background image like this:
keyboardKey.backgroundColor = UIColor(patternImage: UIImage(named:"KeyBackground")!)
But is looks bad...
What am i doing wrong?
The background looks like that because the size of your image is a little smaller than the size of each key, and the image is being tiled (repeated to fill the space - so each key contains the background image and pieces of the surrounding tiles to the right, bottom, and bottom-right).
The UIColor(patternImage:) docs say that
During drawing, the image in the pattern color is tiled as necessary to cover the given area.
Your first solution probably didn't work because you inserted your UIImageView as the bottom-most subview - I assume another (opaque) view was on top of it. What type of UIControl are you using? UIButton, for example, already provides some "background image" support out of the box.
Oh, it's so easy. My keyboardKey.frame, at the time of the call this method, have frame = 0, 0, 0, 0.
Night a bad time for work:)

How to set image only for a certain part of title in UIButton

I have a UIButton that has a title.
Consider it as Thu 20.
There is an image which is set as the background for the button,this image is width resized and content mode is aspect fit.So it appears to the full button.
My problem:
I need the image only for the "20" part and not the entire "Thu 20".
if you see the iPad calendar you will get the point.
How can i do it?
Thanks
Unless something like an attributed string has a functionality of putting a rectangle around some text I suggest you to create a custom view that handles this:
You would need a view that excepts 2 strings, a normal one and the one in the rectangle. Then insert a normal label, set the text, call sizeToFit. Then add the second label and do the same as first but in the end place it right next to the first label. Then add the image view onto the label respecting the labels dimensions. Then resize the whole view to fit all the views on it. Now you can add this custom label to a button or anywhere you want it...
I would rather suggest you place an Image VIew containing your Image and Then place a Label on top of Image with your "20" over it and then Place a custom UIButton with No backGround Color, and No Images on the top of both
With a bit of work you could override one of these UIButton methods to place the image at the correct location. Using a method within UILabel to determine the coordinates of "20" would give the the location for the image.
class UIButton {
// these return the rectangle for the background (assumes bounds), the content (image + title) and for the image and title separately. the content rect is calculated based
// on the title and image size and padding and then adjusted based on the control content alignment. there are no draw methods since the contents
// are rendered in separate subviews (UIImageView, UILabel)
open func backgroundRect(forBounds bounds: CGRect) -> CGRect
open func contentRect(forBounds bounds: CGRect) -> CGRect
open func titleRect(forContentRect contentRect: CGRect) -> CGRect
open func imageRect(forContentRect contentRect: CGRect) -> CGRect
}
See Apple's Documentation under "Getting Dimensions" for more information about these methods.

Resources