Can't add constraints to video programmatically - swift

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
}
}

Related

How can I set priority on constraints in Swift?

I've been struggling to make priority on constraints work programmatically in Swift.
My goal is to have the meetingFormView no more than 300 wide. Using IB I would give the width constraint a lower priority and give a higher priority to the "lessThanOrEqualToConstant". But I can't get it to work.
I've tried this:
meetingFormView.translatesAutoresizingMaskIntoConstraints = false
let constraintWidth = NSLayoutConstraint(
item: meetingFormView,
attribute: NSLayoutAttribute.width,
relatedBy: NSLayoutRelation.equal,
toItem: startView,
attribute: NSLayoutAttribute.width,
multiplier: 1,
constant: 0)
constraintWidth.priority = .defaultHigh
NSLayoutConstraint.activate([
meetingFormView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
meetingFormView.heightAnchor.constraint(equalToConstant: 170),
meetingFormView.widthAnchor.constraint(lessThanOrEqualToConstant: 300),
meetingFormView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
constraintWidth
])
It actually seems to take three lines of code to set up a prioritized "anchor-based" constraint in code:
let widthConstraint = meetingFormView.widthAnchor.constraint(equalToConstant: 170)
widthConstraint.priority = UILayoutPriority(rawValue: 500)
widthConstraint.isActive = true
It seems like if I try to set isActive with the let declare Xcode (maybe Swift?) doesn't recognize that the type is NSLayoutConstraint. And using UILayoutPriority(rawValue:) seems to be the best (only?) way to set priorities.
While this answer doesn't conform exactly with what you are doing, I believe it will work with IB. Just replace the let with creating an IBOutlet and there should be no need for the isActive line.
Obviously, to change the priority later in code you just need:
widthConstraint.priority = UILayoutPriority(rawValue: 750)
You can write a small extension like this:
extension NSLayoutConstraint
{
func withPriority(_ priority: Float) -> NSLayoutConstraint
{
self.priority = UILayoutPriority(priority)
return self
}
}
then use it like this:
NSLayoutConstraint.activate([
...
meetingFormView.widthAnchor.constraint(lessThanOrEqualToConstant: 300).withPriority(500),
...
])

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)

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 :)

How to add constraints to a dynamically created UITextField?

I have created UITextFields dynamically. Now i want to refer to the TextFields to check for some constraints. How do i do so?
func displayTextBox1(height: Int, placeHolder: String, xtb: Int, ytb: Int, lableName: String, xl: Int, yl: Int) {
DispatchQueue.main.async{
self.textField = UITextField(frame: CGRect(x: xtb, y: ytb, width: 343, height: height))
self.textField.textAlignment = NSTextAlignment.left
self.textField.textColor = UIColor.black
self.textField.borderStyle = UITextBorderStyle.line
self.textField.autocapitalizationType = UITextAutocapitalizationType.words // If you need any capitalization
self.textField.placeholder = placeHolder
print("hi")
self.view.addSubview(self.textField)
self.displayLabel(labelName: lableName, x: xl, y: yl)
}
}
You can set constraints programmaticaly using the sample explained code below:
let constraint = NSLayoutConstraint(item: self.textField, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: -50)
NSLayoutConstraint.activate([constraint])
As you can see, I am creating a constraint for item textField which width should be equal to width of view multiplied by 1 minus 50. That means the width of your textField will be 50 pixels less than the width of the view. The last line of code activates given set of created constraints.
Welcome to Stackoverflow, and hope you're enjoying learning Swift!
I'm going to make some assumptions based on your code snippet:
the details you need to create the textfield and label (position, placeholder text etc) are coming from some service that operates on a background thread (perhaps a HTTP request?) which is why you're using DispatchQueue.main.async to perform UI events back on the main thread.
there will be multiple textfield/label pairs that you're going to configure and add to the interface (not just a single pair)... perhaps a 'todo' list sort of app where a label and textfield let people enter a note (more on this in part 2 of answer) which is why these views (and constraints) are being created in code rather than in a storyboard.
you want to invest in a constraint-based layout rather than frame-based positioning.
Answer part 1
If any of those assumptions are incorrect, then parts of this answer probably won't be relevant.
But assuming the assumptions are correct I suggest a couple things:
Use a separate helper methods to create a textfield/view and return the result (rather than doing everything in a single method) -- methods that have a single purpose will make more sense and be easier to follow.
Don't use a mixture of setting view position/size with frame and constraints - use one approach or the other (since you're new it will be easier to keep a single mental model of how things are working rather than mixing).
Here's a snippet of what a view controller class might start to look like:
import UIKit
class ViewController: UIViewController {
func triggeredBySomeEvent() {
// assuming that you have some equivilent to `YouBackgroundRequestManager.getTextFieldLabelDetails`
// that grabs the info you need to create the label and textfield on a background thread...
YouBackgroundRequestManager.getTextFieldLabelDetails { tfPlaceholder, tfHeight, tfX, tfY, labelName, labelX, labelY in
// perform UI work on the main thread
DispatchQueue.main.async{
// use our method to dynamically create a textfield
let newTextField: UITextField = self.createTextfield(with: tfPlaceholder, height: tfHeight)
// add textfield to a container view (in this case the view controller's view)
self.view.addSubview(newTextField)
// add constraints that pin the textfield's left and top anchor relative to the left and top anchor of the view
newTextField.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: tfX).isActive = true
newTextField.topAnchor.constraint(equalTo: self.view.topAnchor, constant: tfY).isActive = true
// repeat for label...
let newLabel: UILabel = self.createLabel(with: labelName)
self.view.addSubview(newLabel)
newLabel.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: labelX).isActive = true
newLabel.topAnchor.constraint(equalTo: self.view.topAnchor, constant: labelY).isActive = true
}
}
}
// create, configure, and return a new textfield
func createTextfield(with placeholder: String, height: CGFloat) -> UITextField {
let textField = UITextField(frame: .zero) // set the frame to zero because we're going to manage this with constraints
textField.placeholder = placeholder
textField.textAlignment = .left
textField.textColor = .black
textField.borderStyle = .line
textField.autocapitalizationType = .words
// translatesAutoresizingMaskIntoConstraints = false is important here, if you don't set this as false,
// UIKit will automatically create constraints based on the `frame` of the view.
textField.translatesAutoresizingMaskIntoConstraints = false
textField.heightAnchor.constraint(equalToConstant: height).isActive = true
textField.widthAnchor.constraint(equalToConstant: 343.0).isActive = true
return textField
}
// create, configure and return a new label
func createLabel(with labelName: String) -> UILabel {
let label = UILabel()
label.text = labelName
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
}
Answer part 2
I'm struggling to imagine the situation where you actually want to do this. If you are making a UI where elements repeat themselves (like a todo list, or maybe a spreadsheet-type interface) then this approach is not the right way to do.
If you want to create a UI where elements repeat themselves as repeating elements in a single column you should investigate using a UITableViewController where you create a cell that represents a single element, and have a tableview manage that collection of cells.
If you want to create a UI where elements repeat themselves in any other way than a vertical list, then you investigate using UICollectionViewController which is a little more complex, but a lot more powerful.
Apologies if this answer goes off-topic, but hope that inspires some ideas that are useful for you.