How to fix UIImageView Aspect Fit after download - swift

I'm setting the image view to aspect-fit and resizing it after the image is downloaded, to remove the extra padding on top and bottom. What I have works fine unless the first image doesn't fill the whole screen, it will crop the second image incorrectly. If I back out of the screen and go back in it's fixed. Any idea how to fix this? I've added an estimated row height as well.
Inside my cell:
let mainImageUrl = URL(string: noteImage!.imageUrl)
self.noteImageView.kf.indicatorType = .activity
self.noteImageView.kf.setImage(with: mainImageUrl, completionHandler: {
(image, error, cacheType, imageUrl) in
if image != nil {
let constraint = NSLayoutConstraint(
item: self.noteImageView, attribute: NSLayoutAttribute.height,
relatedBy: NSLayoutRelation.equal,
toItem: self.contentView, attribute: NSLayoutAttribute.width,
multiplier: (image!.size.height - 30) / (image!.size.width), constant: 0.0)
self.contentView.addConstraint(constraint)
} else {
print("error downloading image")
}
})
Here is what it looks like, scenario 1 with a portrait tall image works fine. Scenario 2 with a short image crops it weird.

Related

How to change the UIAlertController's width in Swift 4?

Here is one way to change the AlertController's width:
let alertController = UIAlertController(title: "Disconnected", message: "", preferredStyle: .alert)
let width : NSLayoutConstraint = NSLayoutConstraint(item: alertController.view, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 160)
alertController.view.addConstraint(width)
But there will be an error message when running:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(
"<NSLayoutConstraint:0x60000009ae50 UIView:0x7f9c4473cbf0.width == 270 (active)>",
"<NSLayoutConstraint:0x6040002824e0 _UIAlertControllerView:0x7f9c460d4e00'Disconnected'.width >= UIView:0x7f9c4473cbf0.width (active)>",
"<NSLayoutConstraint:0x60c00008be00 _UIAlertControllerView:0x7f9c460d4e00'Disconnected'.width == 160 (active)>"
)
Is there any other way to change the AlertController's width? Or is there any way to fix the error?
Any suggestions would be appreciated.

Get the center of screen with Navigation Bar considerations

I am trying to center a UIActivityIndicator and reuse it throughout my app. I'm facing an issue where some of my views have to be under the navigation bar, and some aren't. This happens to give my activity indicator a lower than center presentation on views where the view is not under the navigation bar. For instance:
All the answers on StackOverflow seem to fall under picture #2.
I've simply made an extension of UIView to show and hide the activity indicator by adding it as a subview overlay. Checking multiple posts on StackOverflow, I didn't find anything that would duplicate this question.
My question is, in a production app, how would one successfully center this view for both of these situations utilizing only one function but still support IOS version back to 9.3
SWIFT 3:
Extension centers the subview in your App delegate's keyWindow...making it centered no matter where it is in your application:
extension UIView {
public func addSubviewScreenCenter() {
if let keyWindow = UIApplication.shared.keyWindow {
keyWindow.addSubview(self)
self.center()
}
}
public func removeFromWindowView() {
if self.superview != nil {
self.removeFromSuperview()
}
}
}
you can get Hight of Screen + Top and Bottom
var height: CGFloat {
let heightTop = UIApplication.shared.statusBarFrame.height + (
(self.navigationController?.navigationBar.intrinsicContentSize.height) ?? 0)
let heightBottom = self.tabBarController?.tabBar.frame.height ?? 0
return UIScreen.main.bounds.height + (heightTop+heightBottom)
}
// then
indicator.center = CGPoint(x: self.view.frame.width/2.0, y: height/2.0 )
you can use safeAreaLayoutGuide it give you area of visible screen
let indicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.translatesAutoresizingMaskIntoConstraints = false
return indicator
}()
self.view.addSubview(indicator)
let safeAreaGuide = self.view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
indicator.centerXAnchor.constraint(equalTo: saveAreaGuide.centerXAnchor),
indicator.centerYAnchor.constraint(equalTo: saveAreaGuide.centerYAnchor)
])
There are methods on UIView to convert points and frames between different view coordinate systems (supported on iOS 2.0+, https://developer.apple.com/documentation/uikit/uiview/1622424-convert).
extension UIView {
func centerInContainingWindow() {
guard let window = self.window else { return }
let windowCenter = CGPoint(x: window.frame.midX, y: window.frame.midY)
self.center = self.superview.convert(windowCenter, from: nil)
}
}
A UIView specifies its coordinates relative to its superview, so centerInContainingWindow() uses UIView.convert(_:from:) to obtain the center of the window in coordinates relative to its superview.
Picture #1, view under navigation bar: the view fills the entire window, so the center point before and after converting will be the same.
Picture #2, view not under navigation bar: the view fills only part of the window, so the window center after converting will be shifted to remain in the same place on screen.
You can use this:
func setActivityIndicatorTo(view: UIView, indicator: UIActivityIndicatorView){
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
}
Or you can found center of screen below:
let w = UIScreen.main.bounds.width
let h = UIScreen.main.bounds.height
activity.center = CGPoint(x: w / 2, y: h / 2)

Can't add constraints to video programmatically

I am trying to programmatically constraint a video into the center of the page. My AV controller is called avPlayerController .
I have already given its x and y values along with the width and height:
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
So how do i center it?
I HAVE TRIED: Programmatically Add CenterX/CenterY Constraints
But, as you can guess it did not work :(
Here is my code:
super.viewDidLoad()
let filepath: String? = Bundle.main.path(forResource: "rockline", ofType: "mp4")
let fileURL = URL.init(fileURLWithPath: filepath!)
avPlayer = AVPlayer(url: fileURL)
let avPlayerController = AVPlayerViewController()
avPlayerController.player = avPlayer
avPlayerController.view.frame = CGRect(x: 36 , y: 20, width: 343, height: 264)
// hide/show control
avPlayerController.showsPlaybackControls = false
// play video
avPlayerController.player?.play()
self.view.addSubview(avPlayerController.view)
avPlayerController.view.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
Here's the code, with explanation.
Always remember that if you are using auto layout constraints, do not set frames. The layout engine will walk all over them. If you are instantiating your view in code, don't set a frame, or if necessary, it communicates things best if you set the frame to CGRect.zero.
Understand the view life cycle. Specifically, you can set your constraints in viewDidLoad, where they should be created only once.
Remember to set the auto resizing mask to false. This is the most common error when you learning auto layout in code.
There are actually three ways to create constraints, and a few ways to activate them. In your question, I think the easiest way is to use anchors.
Here's an example of centering a view (any view) with a width of 343 and a height of 264:
let myView = UIView() // note, I'm not setting any frame
override func viewDidLoad() {
super.viewDidLoad()
view.translatesAutoresizingMaskIntoConstraints = false
myView.translatesAutoresizingMaskIntoConstraints = false
myView.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
myView.heightAnchor.constraint(equalToConstant: 264.0).isActive = true
myView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
myView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
That's all there is to it! BUT....
I'd suggest one more thing. Don't use constants in setting the height and width. That's not being "adaptive". Your 4 inch iPhone SE has a screen size of 568x320, where this may look centered and large enough. But on an iPhone Plus with a screen size of 736x414 it may be pretty small. (To say nothing of a 12.9 inch iPad Pro!)
Notice how my code uses the superview for the centerX/centerY anchors. (And instead of equalToConstant it's equalTo.) Do the same with the width and height. Through the use of multiplier and constant, along with UILayoutGuides, you can make your layouts adapt to whatever screen size Apple throws at you.
You can try this.
avPlayerController.view.enableAutoLayoutConstraint()
avPlayerController.view.setCenterXConstraint(.equal, constantValue: 0)
avPlayerController.view.setCenterYConstraint(.equal, constantValue: 0)
extension UIView
{
//Method to making translatesAutoresizingMaskIntoConstraints false.
func enableAutoLayoutConstraint()
{
self.translatesAutoresizingMaskIntoConstraints = false
}
//Method to set Center-X Consttraint
func setCenterXConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerX, relatedBy: relationType, toItem: self.superview, attribute: .centerX, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
//Method to set Center-Y Consttraint
func setCenterYConstraint(_ relationType:NSLayoutRelation , constantValue:CGFloat)->NSLayoutConstraint
{
let constraint = NSLayoutConstraint(item: self, attribute:.centerY, relatedBy: relationType, toItem: self.superview, attribute: .centerY, multiplier: 1, constant: constantValue)
self.superview?.addConstraint(constraint)
return constraint
}
}

Customize UIView as background

I would like to use UIView to create the dark gray curvy as a design.
However, I think I am doing it wrong when I try to set the cornerRadius and I know cornerRadius is only for the corner. Maybe I am not aware that there is actually another way to go around and would like to know if there an easy way to go around. The way I am doing this is, I created an UIView on StoryBoard and I create a Cocoa Touch Class that is Subclass of UIView and have configure the Custom Class to inherit the traits.
Any suggestion is really appreciated here.
That curve is not something you will be able to achieve using cornerRadius. There are two easy ways you can have what you want: A pre-rendered UIImage or a custom drawn CAShapeLayer.
Option 1: UIImage Background
You can create a background image in your favorite image editor, then set it as a centered background:
// create the UIImageView to display the image
let backgroundImageView = UIImageView()
backgroundImageView.image = UIImage(named: "background")
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(backgroundImageView)
// create constraints to position the UIImageView and apply them
let horizontalConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
view.addConstraints([horizontalConstraint, verticalConstraint])
Put that in the viewDidLoad() of the UIViewController you want to have a background.
You'll need to create your background image sized for the largest screen you're targeting and add it to your app's assets. You can experiment with setting constraints on the size to get it just the way you want it on every screen.
The advantage of this method is that if you want your background to be something more elaborate, you can just edit the image.
Option 2: CAShapeLayer Background
For your specific case (a grey curve) you can do it pretty easily with vectors:
// decide on size of ellipse and how to center it
let ellipseWidth:CGFloat = view.frame.width * 7
let ellipseHeight:CGFloat = view.frame.height * 3
let ellipseTop:CGFloat = 100
let ellipseLeft:CGFloat = -ellipseWidth / 2 + view.frame.width / 2
// create the ellipse path
let ellipseOrigin = CGPoint(x:ellipseLeft,y:ellipseTop)
let ellipseSize = CGSize(width: ellipseWidth, height: ellipseHeight)
let ellipsePath = CGPath(ellipseIn: CGRect(origin: ellipseOrigin, size: ellipseSize), transform: nil)
// create the shape layer
let shapeLayer = CAShapeLayer()
shapeLayer.frame = view.frame
shapeLayer.fillColor = UIColor.darkGray.cgColor
shapeLayer.path = ellipsePath
view.layer.addSublayer(shapeLayer)
You might have to experiment with the proportions of the ellipse to get it to look just right. 7x3 seems to look pretty close to your example.
If you want to get fancier you can add other shapes, strokes, gradients, etc., or you can use a UIBezierPath to get exactly the right curve.
Also note for this to work you'll need
import CoreGraphics
at the top of your file.

Update an UITextView size that is set via NSLayout in Swift 3

I have a variable in my UIViewController that is tied to a constraint setting up the UITextView's height:
var textViewHeight: Int!
Here is the constraint:
self.view.addConstraintsWithFormat(format: "V:|-74-[v0(\(textViewHeight!))]", views: self.textView)
I use this extension:
extension UIView
{
func addConstraintsWithFormat(format: String, views: UIView...)
{
var viewDict = [String: AnyObject]()
for (index, view) in views.enumerated()
{
view.translatesAutoresizingMaskIntoConstraints = false
let key = "v\(index)"
viewDict[key] = view
}
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewDict))
}
}
I have set up a Notification that triggers when the keyboard shows up.
It is triggered correctly (I have a print and it always fires correctly) and the function that is executed includes this code:
if let keyboardSize = sender.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? CGRect {
print(keyboardSize.height)
self.textViewHeight = Int(self.view.frame.height-keyboardSize.height-100)
self.view.updateConstraints()
}
The keyboard's height is printed correctly but the text view's height is not changed.....
Thank you in advance!
Simply setting the constraints once with visual format will not update the constraints later if the variable value (in this case textViewHeight) changes later. So, you'd have to actually set up a constraint via code that can be modified later as the textViewHeight value changes.
Here are the changes you'd need:
1: Add a variable to hold a reference to the constraint you'll want to modify later.
var heightConstraint:NSLayoutConstraint!
2: Create the constraints for your text view individually instead of using the visual format (self.view.addConstraintsWithFormat(format: "V:|-74-[v0(\(textViewHeight!))]", views: self.textView))
// Add vertical constraints individually
let top = NSLayoutConstraint(item:textView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:topLayoutGuide, attribute: NSLayoutAttribute.bottom, multiplier:1.0, constant:74.0)
heightConstraint = NSLayoutConstraint(item:textView, attribute:NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem:nil, attribute:NSLayoutAttribute.notAnAttribute, multiplier:1.0, constant:textViewHeight)
view.addConstraint(top)
view.addConstraint(heightConstraint)
3: You are probably better off changing textViewHeight to CGFloat since all the values you'll deal with there would be CGFloat values rather than Int.
4: Where you get the keyboard notification, after you calculate textViewHeight, add the following line:
self.heightConstraint.constant = textViewHeight
And that should do the trick since now, when textViewHeight changes, the constraint will be updated as well :)