I have a UITableView with custom sections programatically done. I am aiming to have a button in the navigation bar that will trigger a functions that will show or will hide a UIView with an appearing from left and disappearing from right Animation. So far:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
/** Header View **/
let headerView = UITableViewHeaderFooterView()
headerView.backgroundColor = UIColor.VinylRecorderTheme.white
headerView.addBottomBorder(color: UIColor.init(red: 250/255, green: 250/255, blue: 250/255, alpha: 1), height: 1, margins: 0)
headerView.tag = section
headerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(headerViewTouched)))
/* Header ImageView */
let headerImageView = UIImageView()
if let imageForHeader = vinylRecorderHelper.loadThimbnailFromMetadata(url: iCloudAlbumArray[section].urlTrackArray[0].url){
headerImageView.image = imageForHeader
}
else{
headerImageView.image = UIImage(named: "music_album_ic")
}
headerView.addSubview(headerImageView)
headerImageView.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraint(NSLayoutConstraint(item: headerImageView, attribute: .leading, relatedBy: .equal, toItem: headerView, attribute: .leading, multiplier: 1, constant: 8))
headerView.addConstraint(NSLayoutConstraint(item: headerImageView, attribute: .centerY, relatedBy: .equal, toItem: headerView, attribute: .centerY, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: headerImageView, attribute: .height, relatedBy: .equal, toItem: headerView, attribute: .height, multiplier: 0.65, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: headerImageView, attribute: .width, relatedBy: .equal, toItem: headerImageView, attribute: .height, multiplier: 1, constant: 0))
/** Header Button **/
let buttonWidth = currentDeviceTraitStatus == .wreghreg ? CGFloat(34):CGFloat(16)
let headerExpandButton = UIButton()
headerExpandButton.translatesAutoresizingMaskIntoConstraints = false
headerExpandButton.contentMode = .scaleAspectFit
headerExpandButton.setImage(#imageLiteral(resourceName: "arrow_rigth_ic"), for: .normal)
headerExpandButton.setImage(#imageLiteral(resourceName: "arrow_down_ic"), for: .selected)
headerExpandButton.tag = expandButtonTag
headerView.addSubview(headerExpandButton)
headerView.addConstraint(NSLayoutConstraint(item: headerExpandButton, attribute: .trailing, relatedBy: .equal, toItem: headerView, attribute: .trailing, multiplier: 1, constant: -8))
headerView.addConstraint(NSLayoutConstraint(item: headerExpandButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: buttonWidth))
headerView.addConstraint(NSLayoutConstraint(item: headerExpandButton, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: buttonWidth))
headerView.addConstraint(NSLayoutConstraint(item: headerExpandButton, attribute: .centerY, relatedBy: .equal, toItem: headerView, attribute: .centerY, multiplier: 1, constant: 0))
/** Header Label **/
let headerNameLabel = UILabel()
headerNameLabel.text = "\(iCloudAlbumArray[section].artistName) - \(iCloudAlbumArray[section].albumName)"
headerNameLabel.font = currentDeviceTraitStatus == .wreghreg ? .boldSystemFont(ofSize: 18):.boldSystemFont(ofSize: 15)
headerView.addSubview(headerNameLabel)
headerNameLabel.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraint(NSLayoutConstraint(item: headerNameLabel, attribute: .left, relatedBy: .equal, toItem: headerImageView, attribute: .right, multiplier: 1, constant: 8))
headerView.addConstraint(NSLayoutConstraint(item: headerNameLabel, attribute: .top, relatedBy: .equal, toItem: headerView, attribute: .top, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: headerNameLabel, attribute: .bottom, relatedBy: .equal, toItem: headerView, attribute: .bottom, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: headerExpandButton, attribute: .leading, relatedBy: .equal, toItem: headerNameLabel, attribute: .trailing, multiplier: 1, constant: 8))
/** Remove Button Label **/
let removeButton = UIButton()
removeButton.backgroundColor = UIColor.VinylRecorderTheme.redApple
removeButton.tag = deleteButtonTag
headerView.addSubview(removeButton)
removeButton.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .leading, relatedBy: .equal, toItem: headerView, attribute: .leading, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .top, relatedBy: .equal, toItem: headerView, attribute: .top, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .bottom, relatedBy: .equal, toItem: headerView, attribute: .bottom, multiplier: 1, constant: 0))
removeButton.widthAnchor.constraint(equalToConstant: 0).isActive = true
removeButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(removeViewTouched)))
return headerView
}
And the function that will perform the desired Animation is:
#IBAction func startStopEditAction(_ sender: UIButton) {
print("\(logClassName): startStopEditAction.")
sender.isSelected = !sender.isSelected
if let sectionView = iCloudExportsTableView.headerView(forSection: 0){
print("\(logClassName): startStopEditAction in header")
let removeButton:UIButton = sectionView.viewWithTag(deleteButtonTag) as! UIButton
//removeButton.isHidden = !removeButton.isHidden
//removeButton.widthAnchor.constraint(equalToConstant: isEditingRemove ? 60 : 0)
UIView.transition(with: view, duration: 1.5, options: .transitionCrossDissolve, animations: {
removeButton.widthAnchor.constraint(equalToConstant: 0).isActive = false
removeButton.widthAnchor.constraint(equalToConstant: 60).isActive = true
//removeButton.isHidden = !removeButton.isHidden
})
}
}
EDIT
The UIView (Button)is
let removeButton = UIButton()
After some work around I have been able to achieve what I was looking for.
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
/** Header View **/
let headerView = UITableViewHeaderFooterView()
headerView.backgroundColor = UIColor.VinylRecorderTheme.white
headerView.addBottomBorder(color: UIColor.init(red: 250/255, green: 250/255, blue: 250/255, alpha: 1), height: 1, margins: 0)
headerView.tag = section
headerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(headerViewTouched)))
**************
/** Remove Button **/
let removeButton = UIButton()
removeButton.backgroundColor = UIColor.VinylRecorderTheme.redApple
removeButton.tag = section
removeButton.setTitle(NSLocalizedString("com.messages.delete", comment: ""), for: .normal)
removeButton.titleLabel?.font = currentDeviceTraitStatus == .wreghreg ? .boldSystemFont(ofSize: 18):.boldSystemFont(ofSize: 15)
removeButton.setTitleColor(UIColor.white, for: .normal)
headerView.addSubview(removeButton)
removeButton.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .leading, relatedBy: .equal, toItem: headerView, attribute: .leading, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .top, relatedBy: .equal, toItem: headerView, attribute: .top, multiplier: 1, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: removeButton, attribute: .bottom, relatedBy: .equal, toItem: headerView, attribute: .bottom, multiplier: 1, constant: 0))
let widthConstraint = NSLayoutConstraint(item: removeButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: self.isEditingRemove ? 90:0)
widthConstraint.identifier = tableViewSectionWidthId
headerView.addConstraint(widthConstraint)
removeButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(removeViewTouched)))
return headerView
}
And then In the IBAction:
#IBAction func startStopEditAction(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
let numberOfSections = iCloudExportsTableView.numberOfSections
for i in 0..<numberOfSections{
if let sectionView = iCloudExportsTableView.headerView(forSection: i){
let filteredConstraints = sectionView.constraints.filter { $0.identifier == tableViewSectionWidthId }
if let widthConstraint = filteredConstraints.first {
widthConstraint.constant = self.isEditingRemove ? 90:0
UIView.animate(withDuration: 0.5, delay: TimeInterval.init(Double(i) * Double(0.20)), options: UIViewAnimationOptions.curveEaseInOut, animations: {
sectionView.layoutIfNeeded()
}, completion: { (finished) in
})
}
}
}
}
Related
As I want to move away from xib and make my layout programmatically, I found that using the same exact constraints doesn't work as I would expect.
I want to make this UITableViewCell
It's a quite simple cell with a small icon to its right as well as an Activity Indicator so I can toggle which one I want to see. They are inside a View and to their left is a label
Those are my constraints in the outline view
And it works perfectly. However when I'm removing the XIB and doing all of the code myself, nothing works anymore
So here's my code:
class StandardRow: UITableViewCell {
private var initialWidth: CGFloat = 20
public var fetching: Bool = false {
didSet {
if (fetching) {
activityIndicator?.startAnimating()
} else {
activityIndicator?.stopAnimating()
}
changeImageWidth()
}
}
public var rightImage: UIImage? = nil {
didSet {
rightImageView?.image = rightImage
changeImageWidth()
}
}
private func changeImageWidth() {
if (activityIndicator?.isAnimating) ?? false || rightImage != nil {
imageWidth?.constant = initialWidth
} else {
imageWidth?.constant = 0
}
}
override func prepareForReuse() {
valueLabel?.text = ""
imageView?.image = nil
rightImage = nil
fetching = false
textLabel?.text = ""
accessoryType = .none
}
//Views
private var imageContainer = UIView()
private var rightImageView = UIImageView()
private var activityIndicator: UIActivityIndicatorView? = UIActivityIndicatorView()
public var valueLabel: UILabel? = UILabel()
private var imageWidth: NSLayoutConstraint? = nil
override init(style: UITableViewCell.CellStyle = .default, reuseIdentifier: String? = nil) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
buildView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
buildView()
}
func buildView() {
contentView.addSubview(valueLabel!)
imageContainer.addSubview(rightImageView)
imageContainer.addSubview(activityIndicator!)
contentView.addSubview(imageContainer)
imageContainer.backgroundColor = .red
}
override func layoutSubviews() {
super.layoutSubviews()
//IMAGE CONTAINER CONSTRAINTS
imageWidth = NSLayoutConstraint(item: imageContainer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: initialWidth)
imageWidth?.priority = UILayoutPriority(rawValue: 999)
imageWidth?.isActive = true
let bottomImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0)
bottomImageContainerConstraint.isActive = true
bottomImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
let topImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0)
topImageContainerConstraint.isActive = true
topImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
let trailingImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 5)
trailingImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
trailingImageContainerConstraint.isActive = true
let centerYImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
centerYImageContainerConstraint.isActive = true
centerYImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
//VALUE LABEL CONSTRAINTS
let trailingValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 5)
trailingValueLabelConstraint.isActive = true
trailingValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)
let centerYValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
centerYValueLabelConstraint.isActive = true
centerYValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)
//ACTIVITY INDICATOR CONSTRAINGS
NSLayoutConstraint(item: activityIndicator!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: activityIndicator!, attribute: .leading, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 11).isActive = false
NSLayoutConstraint(item: activityIndicator!, attribute: .bottom, relatedBy: .equal, toItem: imageContainer, attribute: .bottom, multiplier: 1, constant: 11).isActive = false
NSLayoutConstraint(item: activityIndicator!, attribute: .top, relatedBy: .equal, toItem: imageContainer, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: activityIndicator!, attribute: .centerY, relatedBy: .equal, toItem: imageContainer, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
//RIGHT IMAGE VIEW CONSTRAINTS
NSLayoutConstraint(item: rightImageView, attribute: .trailing, relatedBy: .equal, toItem: activityIndicator!, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .leading, relatedBy: .equal, toItem: rightImageView, attribute: .leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .bottom, relatedBy: .equal, toItem: activityIndicator!, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .top, relatedBy: .equal, toItem: activityIndicator!, attribute: .top, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: rightImageView, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator!, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
//changeImageWidth()
}}
So I have a few ideas to where it can come from, firstly being "translatesAutoresizingMaskIntoConstraints" set to true by default, but when I'm setting it to false in the superview then my cell doesn't show anymore and in the contentView, Xcode tells me I shouldn't do that because of an undefined behaviour
I'm also using Reveal to debug my UI and then I found those peculiar values:
Which is not what I want, Reveal is reporting that those constraints are translating the autoresizing mask of the view to autolayout so it would confirm the previous theory. I did set the priority to 999 to some of the constraints because otherwise they would be broken.
I'm actually at a dead end and I think I'm missing something but I can't pinpoint what as I don't have enough experience with non-interface builder constraints
Try Anchors, it's much easier.
Example
var redView = UIView()
redView.backgroundColor = .red
anyView.addsubView(redView)
redView.translatesAutoresizingMaskIntoConstraints = false
redView.centerXAnchor.constraint(equalTo: self.parentView.centerXAnchor).isActive = true
redView.centerYAnchor.constraint(equalTo: self.parentView.centerYAnchor).isActive = true
redView.heightAnchor.constraint(equalToConstant: 100).isActive = true
redView.widthAnchor.constraint(equalToConstant: 100).isActive = true
You can add the same method to your UIView extension
func constrainToEdges(_ subview: UIView, top: CGFloat = 0, bottom: CGFloat = 0, leading: CGFloat = 0, trailing: CGFloat = 0) {
subview.translatesAutoresizingMaskIntoConstraints = false
let topContraint = NSLayoutConstraint(
item: subview,
attribute: .top,
relatedBy: .equal,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: top)
let bottomConstraint = NSLayoutConstraint(
item: subview,
attribute: .bottom,
relatedBy: .equal,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: bottom)
let leadingContraint = NSLayoutConstraint(
item: subview,
attribute: .leading,
relatedBy: .equal,
toItem: self,
attribute: .leading,
multiplier: 1.0,
constant: leading)
let trailingContraint = NSLayoutConstraint(
item: subview,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: 1.0,
constant: trailing)
addConstraints([
topContraint,
bottomConstraint,
leadingContraint,
trailingContraint])
}
I recommend using this framework for building constraint based layouts programmatically, it makes the process straightforward and faster. Take the setup for the contentView of this cell for example:
contentView.addSubview(descriptionLabel)
contentView.addSubview(amountLabel)
contentView.addSubview(dateLabel)
contentView.addSubview(bottomRightLabel)
constrain(descriptionLabel, amountLabel, dateLabel, bottomRightLabel) { desc, amount, date, bottomRight in
desc.top == desc.superview!.top + 16
desc.left == desc.superview!.left + 16
desc.right <= amount.left + 12
desc.bottom == date.top - 12
amount.centerY == desc.centerY
amount.right == amount.superview!.right - 12
date.left == date.superview!.left + 16
date.right <= bottomRight.left - 12
date.bottom == date.superview!.bottom - 16
bottomRight.centerY == date.centerY
bottomRight.right == bottomRight.superview!.right - 12
}
I am loading a custom view class from the nib at viewWillAppear. After loading the nib, I add it as a subview, then apply some constraints, then update the constraints. At that point I attempt to add a shadow to this view, but no shadow is displaying....
Just for some context, the ViewController has scrollview that contains a contentView. All of the view's content goes inside this contentView.
Here is my code:
ViewController
class MyViewController: UIViewController {
#IBOutlet weak var contentView: UIView!
var matches: [Match] = []
var matchViews: [MatchDetailsView] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setMatches()
}
func setMatches() {
// Match One
let matchOneDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 0 ? matches[0] : nil, delegate: self, tag: 1)
contentView.addSubview(matchOneDetailsView)
matchOneDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchOneLeadingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchOneTrailingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchOneTopConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 32)
// Match Two
let matchTwoDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 1 ? matches[1] : nil, delegate: self, tag: 2)
contentView.addSubview(matchTwoDetailsView)
matchTwoDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchTwoLeadingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchTwoTrailingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchTwoTopConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .top, relatedBy: .equal, toItem: matchOneDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
// Match Three
let matchThreeDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 2 ? matches[2] : nil, delegate: self, tag: 3)
contentView.addSubview(matchThreeDetailsView)
matchThreeDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchThreeLeadingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchThreeTrailingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchThreeTopConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .top, relatedBy: .equal, toItem: matchTwoDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
let matchThreeBottomConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: -32)
NSLayoutConstraint.activate([matchOneLeadingConstraint, matchOneTrailingConstraint, matchOneTopConstraint, matchTwoLeadingConstraint, matchTwoTrailingConstraint, matchTwoTopConstraint, matchThreeLeadingConstraint, matchThreeTrailingConstraint, matchThreeTopConstraint, matchThreeBottomConstraint])
updateViewConstraints()
matchViews.append(matchOneDetailsView)
matchViews.append(matchTwoDetailsView)
matchViews.append(matchThreeDetailsView)
styleMatchViews()
}
func styleMatchViews() {
for view in matchViews {
view.addShadow(opacity: 0.25, yOffset: 0, xOffset: 0, radius: 5.0)
view.roundCorners(withRadius: 5.0)
}
}
}
MatchDetailsView
class MatchDetailsView: UIView {
class func instanceFromNib(match: Match?, delegate: MatchDetailsViewDelegate, tag: Int) -> MatchDetailsView {
let view = UINib(nibName: "MatchDetailsView", bundle: nil).instantiate(withOwner: MatchDetailsView(), options: nil)[0] as! MatchDetailsView
view.translatesAutoresizingMaskIntoConstraints = false
view.updateMatchContent()
return view
}
}
UIViewExtension
extension UIView {
/**
Add Shadow
Puts a Drop Shadow in the UIView
*/
func addShadow(opacity: Float, yOffset: Int, xOffset: Int, radius: CGFloat) {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = opacity
self.layer.shadowOffset = CGSize(width: xOffset, height: yOffset)
self.layer.shadowRadius = radius
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
}
}
If MatchDetailsView's nib file has 'clipsToBounds' set to false, why isn't my shadow showing up when the page is loaded? Am I missing a layout function even though they seem to be laid out correctly?
You are setting the .shadowPath to the bounds of the view... but the bounds at that point may not be (probably will not be) the bounds after auto-layout lays out the views.
For a "shadowed view" you are much better off setting the shadow inlayoutSubviews() inside the view itself.
You should also add and setup your subviews in viewDidLoad() viewWillAppear() may be called more than once (depending on your navigation), and you'll end up adding multiple copies of your subviews.
So...
Change your MatchDetailsView class like this:
class MatchDetailsView: UIView {
var opacity: CGFloat = 0.0
var xOffset: CGFloat = 0.0
var yOffset: CGFloat = 0.0
// for the shadowRadius
var sRadius: CGFloat = 0.0
// for the cornerRadius
var cRadius: CGFloat = 0.0
override func layoutSubviews() {
self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOpacity = Float(opacity)
self.layer.shadowOffset = CGSize(width: xOffset, height: yOffset)
self.layer.shadowRadius = sRadius
self.layer.shadowPath = UIBezierPath(rect: self.bounds).cgPath
self.layer.cornerRadius = cRadius
}
class func instanceFromNib(match: Match?, delegate: MatchDetailsViewDelegate, tag: Int) -> MatchDetailsView {
let view = UINib(nibName: "MatchDetailsView", bundle: nil).instantiate(withOwner: MatchDetailsView(), options: nil)[0] as! MatchDetailsView
view.translatesAutoresizingMaskIntoConstraints = false
view.updateMatchContent()
return view
}
}
Then, in your view controller:
override func viewDidLoad() {
super.viewDidLoad()
setMatches()
}
func setMatches() {
// Match One
let matchOneDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 0 ? matches[0] : nil, delegate: self, tag: 1)
contentView.addSubview(matchOneDetailsView)
matchOneDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchOneLeadingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchOneTrailingConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchOneTopConstraint = NSLayoutConstraint(item: matchOneDetailsView, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 32)
// Match Two
let matchTwoDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 1 ? matches[1] : nil, delegate: self, tag: 2)
contentView.addSubview(matchTwoDetailsView)
matchTwoDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchTwoLeadingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchTwoTrailingConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchTwoTopConstraint = NSLayoutConstraint(item: matchTwoDetailsView, attribute: .top, relatedBy: .equal, toItem: matchOneDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
// Match Three
let matchThreeDetailsView = MatchDetailsView.instanceFromNib(match: matches.count > 2 ? matches[2] : nil, delegate: self, tag: 3)
contentView.addSubview(matchThreeDetailsView)
matchThreeDetailsView.translatesAutoresizingMaskIntoConstraints = false
let matchThreeLeadingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .leading, relatedBy: .equal, toItem: contentView, attribute: .leading, multiplier: 1, constant: 16)
let matchThreeTrailingConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: -16)
let matchThreeTopConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .top, relatedBy: .equal, toItem: matchTwoDetailsView, attribute: .bottom, multiplier: 1, constant: 32)
let matchThreeBottomConstraint = NSLayoutConstraint(item: matchThreeDetailsView, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: -32)
NSLayoutConstraint.activate([matchOneLeadingConstraint, matchOneTrailingConstraint, matchOneTopConstraint, matchTwoLeadingConstraint, matchTwoTrailingConstraint, matchTwoTopConstraint, matchThreeLeadingConstraint, matchThreeTrailingConstraint, matchThreeTopConstraint, matchThreeBottomConstraint])
matchViews.append(matchOneDetailsView)
matchViews.append(matchTwoDetailsView)
matchViews.append(matchThreeDetailsView)
styleMatchViews()
}
func styleMatchViews() {
for view in matchViews {
view.opacity = 0.25
view.yOffset = 0.0
view.xOffset = 0.0
view.sRadius = 5.0
view.cRadius = 5.0
}
}
I know this is a hard problem to debug. I will try to explain, you can ask question from comments section.
I want to use message bubbles in my app. I wrote some code for constraints, but it is not working well.
How can i put the left edge of blue bubble to red line?
Here is my code:
When tableview cell loads this method is running:
private func setup() {
bubbleImageView = UIImageView(image: bubbleImage.incoming, highlightedImage: bubbleImage.incomingHighlighed)
bubbleImageView.userInteractionEnabled = true
messageLabel = UILabel(frame: CGRectZero)
messageLabel.font = UIFont.systemFontOfSize(15)
messageLabel.numberOfLines = 0
messageLabel.userInteractionEnabled = true
selectionStyle = .None
contentView.addSubview(bubbleImageView)
bubbleImageView.addSubview(messageLabel)
messageLabel.translatesAutoresizingMaskIntoConstraints = false
bubbleImageView.translatesAutoresizingMaskIntoConstraints = false
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Left, relatedBy: .Equal, toItem: contentView, attribute: .Left, multiplier: 1, constant: 10))
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Top, relatedBy: .Equal, toItem: contentView, attribute: .Top, multiplier: 1, constant: 4.5))
bubbleImageView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Width, relatedBy: .Equal, toItem: messageLabel, attribute: .Width, multiplier: 1, constant: 30))
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: .Bottom, relatedBy: .Equal, toItem: contentView, attribute: .Bottom, multiplier: 1, constant: -4.5))
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .CenterX, relatedBy: .Equal, toItem: bubbleImageView, attribute: .CenterX, multiplier: 1, constant: -2))
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .CenterY, relatedBy: .Equal, toItem: bubbleImageView, attribute: .CenterY, multiplier: 1, constant: -0.5))
messageLabel.preferredMaxLayoutWidth = 218
bubbleImageView.addConstraint(NSLayoutConstraint(item: messageLabel, attribute: .Height, relatedBy: .Equal, toItem: bubbleImageView, attribute: .Height, multiplier: 1, constant: -15))
}
As you can see i am creating bubble background and label for text.
And second method:
func setCell(message:Message) {
messageLabel.text="testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest"
var layoutAttribute: NSLayoutAttribute
var layoutConstant: CGFloat
if message.incoming {
bubbleImageView.image=bgImage.incoming
messageLabel.textColor = UIColor.blackColor()
layoutAttribute = .Left
layoutConstant = 10
}else{
if message.isSent == 1 {
bubbleImageView.image = bgImage.outgoing
}
if message.isSent == 0 {
bubbleImageView.image = bgImage.notYetSent
}
messageLabel.textColor = UIColor.whiteColor()
layoutAttribute = .Right
layoutConstant = -10
}
contentView.addConstraint(NSLayoutConstraint(item: bubbleImageView, attribute: layoutAttribute, relatedBy: .Equal, toItem: contentView, attribute: layoutAttribute, multiplier: 1, constant: layoutConstant))
}
What is the problem with my code? It is working well for incoming messages (gray bubble) and not working well for outgoing messages (blue bubble)
I get it. You have to add a constraint that doesnt allows the bubble to exceed the max width. So you have to add a constraint like that:
let widthConstraint = NSLayoutConstraint(item: youritem, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.LessThanOrEqual,
toItem: self.youItem.superview, attribute: NSLayoutAttribute.Width,
multiplier: 1.0, constant: 100)
func tableView(tableView:UITableView, viewForHeaderInSection section:Int) -> UIView?{
let newlabel = UILabel()
//206-250
newlabel.backgroundColor = UIColor(red: (135/255), green:(206/255), blue: (250/255), alpha: 1)
newlabel.textColor = UIColor(white: 1, alpha: 1)
newlabel.textAlignment = .Right
newlabel.font = newlabel.font.fontWithSize(18)
newlabel.adjustsFontSizeToFitWidth = true
let horizontalcontraint = NSLayoutConstraint(item: newlabel, attribute: .Trailing, relatedBy: .Equal, toItem: view, attribute: .Trailing, multiplier: 1, constant: -20)
NSLayoutConstraint.activateConstraints([horizontalcontraint])
newlabel.constraints
newlabel.text = keys[section]+" - "
return newlabel
}
I'm not sure how to reference the section header in the toItem: part of the constraint. Any advice would be appreciated. The UILabel is pinned up against the right side of the header and looks bad. I need a little spacing in there.
I have modified your code and here it is. Try once.
func tableView(tableView:UITableView, viewForHeaderInSection section:Int) -> UIView?{
let headerView = UIView()
headerView.backgroundColor = UIColor.clearColor()
let newlabel = UILabel()
//206-250
newlabel.backgroundColor = UIColor(red: (135/255), green:(206/255), blue: (250/255), alpha: 1)
newlabel.textColor = UIColor(white: 1, alpha: 1)
newlabel.textAlignment = .Right
newlabel.font = newlabel.font.fontWithSize(18)
newlabel.adjustsFontSizeToFitWidth = true
newlabel.constraints
newlabel.text = keys[section]+" - "
headerView.addSubview(newlabel)
newlabel.translatesAutoresizingMaskIntoConstraints = false
headerView.addConstraint(NSLayoutConstraint(item: newlabel, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: headerView, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: newlabel, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: headerView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 20.0))
headerView.addConstraint(NSLayoutConstraint(item: newlabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: headerView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 0))
headerView.addConstraint(NSLayoutConstraint(item: newlabel, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: headerView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0))
return headerView
}
Within UIView I have a method presentView():
public func presentView() {
UIApplication.sharedApplication().delegate?.window??.addSubview(self)
let blurEffect = UIBlurEffect(style: .Light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.backgroundColor = UIColor.yellowColor()
addSubview(blurEffectView)
let topConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
self.addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
print("newframe: \(blurEffectView.frame)")
}
but sth is wrong because this is the output on console:
newframe: (0.0, 0.0, 0.0, 0.0)
and result is:
Remember about:
blurEffectView.translatesAutoresizingMaskIntoConstraints = false
and then:
let topConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0)
let leadingConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: blurEffectView, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0)
addSubview(blurEffectView)
addConstraints([topConstraint, bottomConstraint, leadingConstraint, trailingConstraint])
layoutIfNeeded()