My code is shown below. It's derived from a YouTube video made 3 years ago. Since the date the video was posted, Swift has updated and the output of the function which is Anchor can no longer be found in scope. I'd like to ask for helping in finding a replacement for the output.
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding:
UIEdgeInsets = .zero, size: CGSize = .zero) -> Anchor {
translatesAutoresizingMaskIntoConstraints = false
var anchoredConstraints = AnchoredConstraints()
if let top = top {
anchoredConstraints.top = topAnchor.constraint(equalTo: top,
constant: padding.top)
}
if let leading = leading {
anchoredConstraints.leading = leadingAnchor.constraint(equalTo:
leading, constant: padding.left)
}
if let bottom = bottom {
anchoredConstraints.bottom = bottomAnchor.constraint (equalTo:
bottom, constant: -padding.bottom)
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant:
size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant:
size.height).isActive = true
}
}
I can't suppose your real intention but I believe this snippet(in Swift 5) may be what are you looking for
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?,
bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding:
UIEdgeInsets = .zero, size: CGSize = .zero) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
layoutMargins = padding
}
}
The following is a playground sample code showing how can you use it. (Please don't forget to include the extension somewhere in the code below if you get it try)
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let label = UILabel()
label.text = "Hello World!"
label.textColor = .black
label.backgroundColor = .gray
view.addSubview(label)
label.anchor(
top: view.topAnchor,
leading: view.leadingAnchor,
bottom: view.bottomAnchor,
trailing: view.trailingAnchor
)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
That's the best I can do to help you with no more information, so please in a next post try to give more details about the intentions of the code.
In addition I would advise you that there are better solutions for pinning views in layout nowadays, then I believe it worths a quick research for alternatives.
I Hope it can be of some help to you.
Related
What am I trying to achieve:
I have a collectionView which represents events in a timeline. These cells (events) have description label that is constrained to the left edge.
When scrolling to events to the left on the timeline, some events are too wide and are not showing the description label immediately (i must scroll to the very beginning of the cell on timeline to see it)
The idea was to setup constraints between cells label to collectionView frame so that you could see event description immediately, but these are different view hierarchies.
What would be the best way to approach this?
What i have tried:
Setting up the constraint crashes the app:
Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to activate constraint with anchors <NSLayoutXAxisAnchor:0x600002cbe700 "UILabel:0x11ef0ea20.leading"> and <NSLayoutXAxisAnchor:0x600002cbf240 "UILayoutGuide:0x6000000289a0'UIScrollView-frameLayoutGuide'.leading"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
Additional Info:
UICollectionViewCompositionalLayout:
UICollectionViewCompositionalLayout { sectionIndex, layoutEnvironment in
let item = data.items[sectionIndex]
let contentWidth = data.contentWidth
return context.coordinator.layout(item, contentWidth)
}
NSCollectionLayoutSection:
var layout: (Item, CGFloat) -> NSCollectionLayoutSection = { item, contentWidth in
let groupSize = NSCollectionLayoutSize(widthDimension: .absolute(contentWidth), heightDimension: .absolute(100))
let itemSizes = item.items.map { NSCollectionLayoutItem(layoutSize: .init(widthDimension: .absolute(CGFloat($0.duration)), heightDimension: .absolute(100)))}
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: itemSizes)
group.interItemSpacing = .fixed(0)
group.contentInsets = .init(top: 0, leading: item.leftGap, bottom: 0, trailing: item.rightGap)
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 0
let header = NSCollectionLayoutBoundarySupplementaryItem(
layoutSize: .init(widthDimension: .absolute(255), heightDimension: .absolute(100)),
elementKind: UICollectionView.elementKindSectionHeader,
alignment: .leading
)}
}
header.pinToVisibleBounds = true
section.boundarySupplementaryItems = [header]
return section
}
CollectionViewCell:
class MyCollectionViewCell: UICollectionViewCell {
static let identifier: String = "MyCollectionViewCell"
var item: DataState.Item = .dummy {
didSet {
descriptionLabel.text = item.title
descriptionTimeLabel.text = item.timeDescription
}
}
var containerView: UIView = {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.layer.cornerRadius = 15
containerView.clipsToBounds = true
return containerView
}()
var bg: UIView = {
let bg = UIView()
bg.translatesAutoresizingMaskIntoConstraints = false
bg.backgroundColor = .black
return bg
}()
var descriptionLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 31, weight: .regular)
return label
}()
var descriptionTimeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = .systemFont(ofSize: 23, weight: .regular)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(containerView)
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
containerView.addSubview(bg)
bg.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
bg.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
bg.trailingAnchor.constraint(equalTo: containerView.trailingAnchor).isActive = true
bg.leadingAnchor.constraint(equalTo: containerView.leadingAnchor).isActive = true
containerView.addSubview(descriptionLabel)
descriptionLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 10).isActive = true
descriptionLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10).isActive = true
containerView.addSubview(descriptionTimeLabel)
descriptionTimeLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -10).isActive = true
descriptionTimeLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 10).isActive = true
}
}
Just to remind whole point is to make labels of cell visible immediately as the wide cell being revealed and be continously updated so when the cell is fully visible label's leadingAnchor and containerView leadingAnchor equals 10
maybe you can try this. If this in my case there 2 separate file. 1 for cell, and 1 viewController with collectionView in it.
I will use UICollectionViewDataSource, UICollectionViewDelegate to the viewController. You can set the cell with add 1 more extention UICollectionViewFlowLayout or
You can add setUpUI direct to the UICollectionViewCell and call it to override func awakeFromNib(). here's the code :
mainView.translatesAutoresizingMaskIntoConstraints = false mainView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width - 40).isActive = true
mainView: UIView! from cell and UIScreen.main.bounds.size.width is the controller collectionView Size.
add reference outlet of collectionView controller to mainView
Don't Forget to check you constraint at cell. and change the autoRezized from "none" to "automatic"
So turns out that if you add constraints to cell as properties and then in: scrollViewDidScroll(UIScrollView) find visibleCells and filter those which are visible just halfway or so. you can then change constants on constraints and update the view.
works great. looks weird.
example:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let collectionView = scrollView as? UICollectionView else { return } // move elsewhere just to access reference to collectionView and dont cast it everytime offset changes (which could be many times a second)
let scrollOffset = scrollView.contentOffset.x
if let visible = collectionView?.visibleCells as? [MyCell] {
let visibleDisappearing = visible.filter({ $0.frame.minX <= scrollOffset })
let visibleFull = visible.filter({ $0.frame.minX > scrollOffset })
for disappearingCell in visibleDisappearing {
let distance = scrollView.contentOffset.x - disappearingCell.frame.minX + 10 // 10 would be the original distance from edge to label
disappearingCell.descriptionLeadingConstraint.constant = distance
disappearingCell.descriptionTimeLeadingConstraint.constant = distance
disappearingCell.layoutIfNeeded()
}
for fullCell in visibleFull {
fullCell.descriptionLeadingConstraint.constant = 10
fullCell.descriptionTimeLeadingConstraint.constant = 10
fullCell.layoutIfNeeded()
}
}
}
I'm having trouble setting constraints to a label programmatically in Swift. I want the label to fill the entire screen. But I dont know how to do.
Thank you for your help.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Hello"
label.backgroundColor = UIColor.yellow
self.view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
let width: NSLayoutConstraint
width = label.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1)
let top: NSLayoutConstraint
top = label.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor)
let bottom: NSLayoutConstraint
bottom = label.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor)
width.isActive = true
bottom.isActive = true
top.isActive = true
}
}
You can use this extension for all of your views
extension UIView {
public func alignAllEdgesWithSuperview() {
guard let superview = self.superview else {
fatalError("add \(self) to a superview first.")
}
self.translatesAutoresizingMaskIntoConstraints = false
let constraints = [
self.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
self.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
self.topAnchor.constraint(equalTo: superview.topAnchor),
self.bottomAnchor.constraint(equalTo: superview.bottomAnchor)
]
NSLayoutConstraint.activate(constraints)
}
}
and the usage
view.addSubview(someView)
someView.alignAllEdgesWithSuperview()
I have been unable to figure out how to solve this issue of mine. I tried to follow the answer from this post. How to change UIView height based on elements inside it
Like the post answer says to do, I have:
set autolayout constraints between UIContainerView top to the UITextView top and UIContainerView bottom to the UITextView bottom (#1)
set height constraint on the text view (#2) and change its constant when resizing the text view (#3)
I have to do this all programmatically. I first set the frame for the container view and give it a specified height. I'm not sure if that is okay too. I also add (#1) in viewDidLoad and am unsure if that's correct.
The text view is not able to increase height either with the current constraints (it is able to if I remove the topAnchor constraint but the container view still doesn't change size).
class ChatController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
lazy var containerView: UIView = {
let containerView = UIView()
containerView.translatesAutoresizingMaskIntoConstraints = false
containerView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height * 0.075)
return containerView
}()
lazy var textView: UITextView = {
let textView = UITextView()
textView.text = "Enter message..."
textView.isScrollEnabled = false
textView.translatesAutoresizingMaskIntoConstraints = false
textView.delegate = self
return textView
}()
override func viewDidLoad() {
super.viewDidLoad()
...
textViewDidChange(self.textView)
addContainerSubViews()
(#1)
containerView.topAnchor.constraint(equalTo: self.textView.topAnchor, constant: -UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
containerView.bottomAnchor.constraint(equalTo: self.textView.bottomAnchor, constant: UIScreen.main.bounds.size.height * 0.075 * 0.2).isActive = true
}
func addContainerSubViews() {
let height = UIScreen.main.bounds.size.height
let width = UIScreen.main.bounds.size.width
let containerHeight = height * 0.075
...//constraints for imageView and sendButton...
containerView.addSubview(self.textView)
self.textView.leftAnchor.constraint(equalTo: imageView.rightAnchor, constant: width/20).isActive = true
self.textView.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -width/20).isActive = true
(#2)
self.textView.heightAnchor.constraint(equalToConstant: containerHeight * 0.6).isActive = true
}
override var inputAccessoryView: UIView? {
get {
return containerView
}
}
(#3)
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
You can do this all with auto-layout / constraints. Because a UITextView with scrolling disabled will "auto-size" its height based on the text, no need to calculate height and change constraint constant.
Here's an example -- it's from a previous answer, modified to include your image view and send button:
class ViewController: UIViewController {
let testLabel: InputLabel = InputLabel()
override func viewDidLoad() {
super.viewDidLoad()
let instructionLabel = UILabel()
instructionLabel.textAlignment = .center
instructionLabel.text = "Tap yellow label to edit..."
let centeringFrameView = UIView()
// label properties
let fnt: UIFont = .systemFont(ofSize: 32.0)
testLabel.isUserInteractionEnabled = true
testLabel.font = fnt
testLabel.adjustsFontSizeToFitWidth = true
testLabel.minimumScaleFactor = 0.25
testLabel.numberOfLines = 2
testLabel.setContentHuggingPriority(.required, for: .vertical)
let minLabelHeight = ceil(fnt.lineHeight)
// so we can see the frames
centeringFrameView.backgroundColor = .red
testLabel.backgroundColor = .yellow
[centeringFrameView, instructionLabel, testLabel].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
view.addSubview(instructionLabel)
view.addSubview(centeringFrameView)
centeringFrameView.addSubview(testLabel)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// instruction label centered at top
instructionLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
instructionLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
// centeringFrameView 20-pts from instructionLabel bottom
centeringFrameView.topAnchor.constraint(equalTo: instructionLabel.bottomAnchor, constant: 20.0),
// Leading / Trailing with 20-pts "padding"
centeringFrameView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
centeringFrameView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
// test label centered vertically in centeringFrameView
testLabel.centerYAnchor.constraint(equalTo: centeringFrameView.centerYAnchor, constant: 0.0),
// Leading / Trailing with 20-pts "padding"
testLabel.leadingAnchor.constraint(equalTo: centeringFrameView.leadingAnchor, constant: 20.0),
testLabel.trailingAnchor.constraint(equalTo: centeringFrameView.trailingAnchor, constant: -20.0),
// height will be zero if label has no text,
// so give it a min height of one line
testLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: minLabelHeight),
// centeringFrameView height = 3 * minLabelHeight
centeringFrameView.heightAnchor.constraint(equalToConstant: minLabelHeight * 3.0)
])
// to handle user input
testLabel.editCallBack = { [weak self] str in
guard let self = self else { return }
self.testLabel.text = str
}
testLabel.doneCallBack = { [weak self] in
guard let self = self else { return }
// do something when user taps done / enter
}
let t = UITapGestureRecognizer(target: self, action: #selector(self.labelTapped(_:)))
testLabel.addGestureRecognizer(t)
}
#objc func labelTapped(_ g: UITapGestureRecognizer) -> Void {
testLabel.becomeFirstResponder()
testLabel.inputContainerView.theTextView.text = testLabel.text
testLabel.inputContainerView.theTextView.becomeFirstResponder()
}
}
class InputLabel: UILabel {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
override var canBecomeFirstResponder: Bool {
return true
}
override var canResignFirstResponder: Bool {
return true
}
override var inputAccessoryView: UIView? {
get { return inputContainerView }
}
lazy var inputContainerView: CustomInputAccessoryView = {
let v = CustomInputAccessoryView()
v.editCallBack = { [weak self] str in
guard let self = self else { return }
self.editCallBack?(str)
}
v.doneCallBack = { [weak self] in
guard let self = self else { return }
self.resignFirstResponder()
}
return v
}()
}
class CustomInputAccessoryView: UIView, UITextViewDelegate {
var editCallBack: ((String) -> ())?
var doneCallBack: (() -> ())?
let theTextView: UITextView = {
let tv = UITextView()
tv.isScrollEnabled = false
tv.font = .systemFont(ofSize: 16)
tv.autocorrectionType = .no
tv.returnKeyType = .done
return tv
}()
let imgView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.clipsToBounds = true
return v
}()
let sendButton: UIButton = {
let v = UIButton()
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .lightGray
autoresizingMask = [.flexibleHeight, .flexibleWidth]
if let img = UIImage(named: "testImage") {
imgView.image = img
} else {
imgView.backgroundColor = .systemBlue
}
let largeConfig = UIImage.SymbolConfiguration(pointSize: 22, weight: .regular, scale: .large)
let buttonImg = UIImage(systemName: "paperplane.fill", withConfiguration: largeConfig)
sendButton.setImage(buttonImg, for: .normal)
[theTextView, imgView, sendButton].forEach { v in
addSubview(v)
v.translatesAutoresizingMaskIntoConstraints = false
}
// if we want to see the image view and button frames
//[imgView, sendButton].forEach { v in
// v.backgroundColor = .systemYellow
//}
NSLayoutConstraint.activate([
// constrain image view 40x40 with 8-pts leading
imgView.widthAnchor.constraint(equalToConstant: 40.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
imgView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8.0),
// constrain image view 40x40 with 8-pts trailing
sendButton.widthAnchor.constraint(equalToConstant: 40.0),
sendButton.heightAnchor.constraint(equalTo: sendButton.widthAnchor),
sendButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -8.0),
// constrain text view with 10-pts from
// image view trailing
// send button leading
theTextView.leadingAnchor.constraint(equalTo: imgView.trailingAnchor, constant: 10),
theTextView.trailingAnchor.constraint(equalTo: sendButton.leadingAnchor, constant: -10),
// constrain image view and button
// centered vertically
// at least 8-pts top and bottom
imgView.centerYAnchor.constraint(equalTo: centerYAnchor),
sendButton.centerYAnchor.constraint(equalTo: centerYAnchor),
imgView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
sendButton.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 8.0),
imgView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
sendButton.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8.0),
// constrain text view 8-pts top/bottom
theTextView.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
theTextView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
])
theTextView.delegate = self
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if (text == "\n") {
textView.resignFirstResponder()
doneCallBack?()
}
return true
}
func textViewDidChange(_ textView: UITextView) {
editCallBack?(textView.text ?? "")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override var intrinsicContentSize: CGSize {
return .zero
}
}
Output:
I'm relatively new to programming in general, and I'm only starting to pickup constraints and auto-layout and how to set things up properly. Currently, what I'm trying to figure out is how to make the top most UITextfield with the UILabel inside of it (The one that says Weight(lbs): 70.0) align to the specs that I have configured for it (See code bellow...).
When I specify the size of the width and length anchors, I always get a View that ignores my size specs and anchors to the entire screen (see image bellow).
Any help would be greatly appreciated.
import UIKit
import RealmSwift
class ThirdViewController: UIViewController {
let realm = try! Realm()
var stats : Results<WeightSetsReps>?
var weightTextField = UITextField()
var weightLabel = UILabel()
var repsTextField = UITextField()
var repsLabel = UILabel()
var timerImage = UIImageView()
var selectedExercise : Exercises? {
didSet{
loadWsr()
}
}
//MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
// timeClock()
navConAcc()
labelConfig()
setTextFieldConstraints()
// setImageViewConstraints()
}
//MARK: - UILabel
func labelConfig(){
weightTextField.placeholder = "Total weight..."
weightTextField.layer.borderWidth = 1
weightTextField.backgroundColor = .white
weightTextField.layer.cornerRadius = 25
weightTextField.layer.borderColor = UIColor.lightGray.cgColor
weightLabel.text = " Weight (lbs): "
weightLabel.textColor = .black
weightTextField.leftView = weightLabel
weightTextField.leftViewMode = .always
repsTextField.placeholder = "Number of Reps..."
repsTextField.layer.borderWidth = 1
repsTextField.backgroundColor = .white
repsTextField.layer.cornerRadius = 25
repsTextField.layer.borderColor = UIColor.lightGray.cgColor
repsLabel.text = " Repetitions: "
repsLabel.textColor = .black
repsTextField.leftView = repsLabel
repsTextField.leftViewMode = .always
[weightTextField, repsTextField].forEach{view.addSubview($0)}
}
//MARK: - TextField Constrainst
func setTextFieldConstraints(){
weightTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, size: .init(width: 20, height: 20))
repsTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, size: .init(width: 20, height: 20))
}
//MARK: - ImageView Constraints
// func setImageViewConstraints(){
//
// timerImage.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom: view.safeAreaLayoutGuide.bottomAnchor, trailing: view.trailingAnchor, padding: .init(top: 40, left: 40, bottom: 450, right: 50))
//
// }
//MARK: - Navigation Bar Setup
func navConAcc(){
navigationItem.title = selectedExercise?.exerciseName
navigationController?.navigationBar.prefersLargeTitles = true
}
//MARK: - Stopwatch
// func timeClock(){
// let image1 = UIImage(named: "stopwatch")
// timerImage = UIImageView(image: image1)
// timerImage.contentMode = .scaleAspectFit
// self.view.addSubview(timerImage)
// }
//MARK: - Load Data
func loadWsr() {
stats = selectedExercise?.wsr.sorted(byKeyPath: "sets", ascending: true)
}
//MARK: - Save Data
func save(wsr : WeightSetsReps){
do {
try realm.write {
realm.add(wsr)
}
} catch {
print("Error saving wsr data \(error)")
}
}
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: padding.right).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}
You need to remove bottom anchor
repsTextField.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.leadingAnchor, bottom:nil, trailing: view.trailingAnchor, size: .init(width: 20, height: 20))
After struggling with programmatic dynamic UI elements for the last few weeks I decided I would give UIstackView a try.
I want custom UIView classes to occupy the stackview with different heights based upon user Input I would remove, add views on the fly.
I found out that stackViews base their 'cell' height upon the UI element's intrinsic content size. However, UIViews do not have one. I searched far and wide and found out that I need to override the View's intrisicContentsize function with one where I can explicitly set the width and height.
However results are very unpredictable and I'm sure there is some little thing that I just do not know dat causes this weird behaviour. Since I'm new to the language and there are a LOT of gotcha's I'm just gonna paste the code here and hope you'll be able to spot what I'm doing wrong.
I've read the docs ofc, a lot of articles, they all point to that override funcion that does not seem to work form me.
This is my mainViewController class;
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var bv = BackButtonView(frame: CGRect.zero, image: UIImage(named: "backArrow.png")!)
var redView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .red
return view
}();
var blueView : UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .blue
return view
}();
let stack : UIStackView = {
let stack = UIStackView();
stack.translatesAutoresizingMaskIntoConstraints = false;
stack.axis = .vertical
stack.distribution = .fillProportionally;
stack.spacing = 8
return stack;
}();
override func viewDidLoad() {
let view = UIView(frame: UIScreen.main.bounds)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
self.view = view
self.view.addSubview(stack)
createLayout()
bv.intrinsicContentSize
stack.addArrangedSubview(bv)
print(bv.frame)
print(bv.intrinsicContentSize)
stack.layoutIfNeeded()
stack.addArrangedSubview(redView)
stack.addArrangedSubview(blueView)
}
private func setConstraints(view: UIView) -> [NSLayoutConstraint] {
return [
view.heightAnchor.constraint(equalToConstant: 50),
]
}
private func createLayout() {
stack.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
stack.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
stack.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
}
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = MyViewController()
Now here's my custom UIClass that is giving me all this trouble:
import UIKit
public class BackButtonView : UIView {
public var size = CGSize(width: 10, height: UIView.noIntrinsicMetric)
override open var intrinsicContentSize: CGSize {
return size
}
var button : UIButton;
public init(frame: CGRect, image: UIImage) {
button = UIButton()
super.init(frame : frame);
self.translatesAutoresizingMaskIntoConstraints = false;
self.backgroundColor = .black
self.addSubview(button)
setupButton(image: image);
print(button.intrinsicContentSize)
}
let backButtonTrailingPadding : CGFloat = -18
lazy var buttonConstraints = [
button.heightAnchor.constraint(equalToConstant: 50),
button.widthAnchor.constraint(equalToConstant: 50),
button.centerYAnchor.constraint(equalTo: self.centerYAnchor),
button.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: backButtonTrailingPadding),
]
private func setupButton(image: UIImage) {
self.button.translatesAutoresizingMaskIntoConstraints = false;
self.button.setImage(image, for: .normal);
self.button.contentMode = .scaleToFill
NSLayoutConstraint.activate(buttonConstraints)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
struct anchors {
var topAnchor = NSLayoutConstraint()
var bottomAnchor = NSLayoutConstraint()
var leadingAnchor = NSLayoutConstraint()
var trailingAnchor = NSLayoutConstraint()
}
}
You can see that the specified width in the size proprrty gets ignored.
Under the above conditions., this is the output
If I now change the my custom UIClass's intrisiContentSize height to anyting else, this happens:
public var size = CGSize(width: UIView.noIntrinsicMetric, height: 10)
override open var intrinsicContentSize: CGSize {
return size
}
result:
Please help me figure out what is and isn't going on
The final screen should look something like this: