Get frame size in updateConstraint function of Custom UIView - ios

I'm setting constraints for views (UILabel, UIImageView) in custom UIView inside UpdateConstraint functions like below. As seen, I'm getting views' height and use it inside auto layout. I know I can get frame size in layoutSubviews function. If I call updateConstraint() function inside layoutSubViews , everything works fine but I don't know If It is the best approach.
In addition when I try to set frame in layoutSubViews() with label.frame = CGRect.. (without auto layout) nothing happens and I can't see custom views inside superview.
override func updateConstraints() {
logoImage.anchor(self.topAnchor, left: self.leftAnchor, bottom: self.bottomAnchor, right: self.rightAnchor, topConstant: 0, leftConstant: 0, bottomConstant: self.frame.height / 2, rightConstant: 0, widthConstant: 0, heightConstant: 0)
label.anchor(self.logoImage.bottomAnchor, left: self.leftAnchor, bottom: nil, right: self.rightAnchor, topConstant: 12, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
super.updateConstraints()
}
override func layoutSubviews() {
print(self.frame.height)
updateConstraints()
}
I searched below posts but can't find any solution;
Where to get frame size of custom UIView in its subclass
Subview of Custom UIView has wrong x frame position

No need to handle that in layoutSubviews if your current constraint create extension sets isActive = true then it will have the correct frame
//
correct way of creating a customView
class CustomView : UIView {
var imageV = UIImageView()
var once = true
override init(frame: CGRect) {
super.init(frame: frame)
sharedLayout()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
sharedLayout()
}
func sharedLayout() {
// set constraints here
self.addSubview(imageV)
}
override func layoutSubviews() {
if once {
self.imageV.translatesAutoresizingMaskIntoConstraints = false
self.imageV.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.imageV.trailingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
self.imageV.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
self.imageV.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
once = false
}
}
}

Related

Vertical scrolling with TableView inside horizontal CollectionView

I have a horizontal UICollectionView (with paging) where each cell has a fullscreen UITableView. The parent ViewController has a navigation bar with large titles where an animation occurs during vertical scrolling. However, since my table views are within the horizontal collection view, the large navigation bar does not animate when scrolling.
I have attempted a solution of setting a delegate for the tableview scrolling and then set the y offset of the collection view but the result glitches and does not work correctly.
Here is my current code (without TableView/CollectionView delegate/datasource methods)
class HomeViewController: UIViewController, HomeCellDelegate {
var delegate: HomeViewControllerDelegate!
var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
create()
}
func create() {
view.backgroundColor = .background
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.sectionInset = .zero
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
collectionView.backgroundColor = .clear
collectionView.showsHorizontalScrollIndicator = false
collectionView.isPagingEnabled = true
collectionView.delegate = self
collectionView.dataSource = self
collectionView.register(HomeCell.self, forCellWithReuseIdentifier: "cellID")
}
//Delegate from UICollectionView cell (the table view)
func tableViewDidScroll(_ contentOffset: CGPoint) {
collectionView.contentOffset.y = contentOffset.y //Glitches
}
}
protocol HomeCellDelegate {
func tableViewDidScroll(_ contentOffset: CGPoint)
}
class HomeCell: UICollectionViewCell {
var events = [String]()
let tableView = UITableView()
var delegate: HomeCellDelegate!
override init(frame: CGRect) {
super.init(frame: frame)
create()
}
func create() {
backgroundColor = .clear
addSubview(tableView)
tableView.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
tableView.contentInsetAdjustmentBehavior = .never
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = CGPoint(x: 0.0, y: scrollView.contentOffset.y + scrollView.adjustedContentInset.top)
delegate.tableViewDidScroll(offset)
}
required init?(coder: NSCoder) {
fatalError()
}
}

How can I get my color gradient to appear in my custom UIView Swift

I have a custom UIView class with UIImageView and UILabel sub views added but I cannot get a color gradient to display on top of them.
Here is my custom UIView class:
//
// HomeTitleView.swift
// Find My Lift
//
// Created by Stephen Learmonth on 13/11/2020.
// Copyright © 2020 Stephen Learmonth. All rights reserved.
//
import UIKit
class HomeTitleView: UIView {
// MARK: - Properties
private let homeTitleImageView: UIImageView = {
let hiv = UIImageView()
hiv.image = UIImage(named: "Stephen")
hiv.contentMode = .scaleAspectFill
hiv.clipsToBounds = true
hiv.setHeight(height: 180)
hiv.backgroundColor = .clear
return hiv
}()
var homeTitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 25)
label.backgroundColor = .clear
label.textColor = .white
return label
}()
// MARK: - Lifecycle
override init(frame: CGRect) {
super.init(frame: frame)
configureUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Helper Functions
private func configureUI() {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.clear.cgColor , UIColor.black.cgColor]
gradient.locations = [0.85, 1]
self.layer.addSublayer(gradient)
gradient.frame = self.bounds
self.addSubview(homeTitleImageView)
homeTitleImageView.anchor(top: safeAreaLayoutGuide.topAnchor, left: leftAnchor,
bottom: bottomAnchor, right: rightAnchor,
paddingTop: 0, paddingLeft: 0,
paddingBottom: 0, paddingRight: 0)
self.addSubview(homeTitleLabel)
homeTitleLabel.anchor(left: leftAnchor, bottom: bottomAnchor,
paddingLeft: 12, paddingBottom: 30)
}
}
I am lacking some knowledge of layers and sub views to understand why it is not working. Any help gratefully received.
Move this line
self.layer.addSublayer(gradient)
end of configureUI , and
override func layoutSubviews() {
super.layoutSubviews()
gradient.frame = self.bounds
}

UICollectionView x3 Within each other creating a collection view slider within a header collection view

I was wondering if anyone can show/help me.
As you can see in image 2 that I am trying to create a card slider, within my UICOLLECTIONVIEW at the top.
I am aware that to create a UICollectionView slider you need to first create the collectionview to hold the slider then another within it. So all together I have 3 UICOLLECTIONVIEWS within each other.
I have been following tutorials to get this far but I can’t figure this one out. Image one is where I am currently.
But currently, all I seem to be doing is overlaying the current UICollection not making it go within it, as you can see in image 1.
[
import UIKit
class MyFeedsHeader: UICollectionViewCell, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let cellId = "appCellId"
let profileImageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .lightGray
return iv
}()
let feedsTitle: UILabel = {
let title = UILabel()
title.text = "Feeds"
title.font = UIFont.boldSystemFont(ofSize: 30)
return title
}()
let followingLabel: UILabel = {
let title = UILabel()
title.text = "00 Following"
title.font = UIFont.boldSystemFont(ofSize: 16)
title.textColor = .lightGray
return title
}()
// --------------------
let appsCollectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
// layout.scrollDirection = .horizontal
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.clear
collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()
//--------------------
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(profileImageView)
profileImageView.anchor(top: self.topAnchor, left: nil, bottom: nil, right: self.rightAnchor, paddingTop: 16, paddingLeft: 0, paddingBottom: 0, paddingRight: 20, width: 50, height: 50)
profileImageView.layer.cornerRadius = 5 / 1
addSubview(feedsTitle)
feedsTitle.anchor(top: self.topAnchor, left: self.leftAnchor, bottom: nil, right: nil, paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
addSubview(followingLabel)
followingLabel.anchor(top: feedsTitle.bottomAnchor, left: self.leftAnchor, bottom: nil, right: nil, paddingTop: 20, paddingLeft: 20, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
setupViews()
self.backgroundColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// ---------------------------------- remove below if can't figure out
func setupViews() {
// --------
appsCollectionView.dataSource = self
appsCollectionView.delegate = self
appsCollectionView.register(AppCell.self, forCellWithReuseIdentifier: cellId)
addSubview(appsCollectionView)
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-8-[v0]-8-|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": appsCollectionView]))
//--------
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
return collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath)
}
class AppCell: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = UIColor.red
}
}
}
I recommend starting with the outermost UICollectionView first, and as you get each one working you can move on to the next.
To get more detail on exactly what is going on with the app currently you can use the View Debugger to see if everything is nested correctly and any constraints are doing what they should.
To get the effect you are looking for your first stop should be implementing the methods coming from UICollectionViewDelegateFlowLayoutlike sizeForItemAt, insetForSectionAt, & minimumLineSpacingForSectionAt. These will define spacing and layout for the cells to interact with.
These delegate methods are all part of the protocol UICollectionViewDelegateFlowLayout which means that your collection view (who's delegate you set to self AKA MyFeedsHeader) will ask your class MyFeedsHeader for information about how to layout the cells if MyFeedsHeader has the functions on it. The is the same thing you did with the func collectionView:numberOfItemsInSection.
Hope that points you in the right direction! Best of luck!

UIStackViews Autolayout throws when either spacing or contentEdgeInsets of UIButton is enabled

I got a UIStackView ParticipateButtonStackView managing three buttons of the same class: ParticipateButton
These buttons only contain an image. To have some padding at the top and bottom, I set UIEdgeInsets. But this and the .spacing = 1 property seem to not work simultaneously. Why?
Stackview:
class ParticipateButtonStackView: UIStackView {
//MARK: - Properties & Variables
var delegate: participateButtonStackViewDelegate?
//MARK: - GUI Objects
var buttons: [ParticipateButton]!
let sureButton = ParticipateButton(type: .sure)
let maybeButton = ParticipateButton(type: .maybe)
let nopeButton = ParticipateButton(type: .nope)
//MARK: - Init & View Loading
override init(frame: CGRect) {
super.init(frame: frame)
self.addBackground(color: UIColor.gray)
buttons = [sureButton, maybeButton, nopeButton]
setupStackView()
setupButtons()
}
//MARK: - Setup
func setupStackView() {
self.axis = .horizontal
self.spacing = 1
self.alignment = .leading
self.distribution = .fillEqually
}
func setupButtons() {
for button in buttons {
addArrangedSubview(button)
button.addTarget(self, action: #selector (self.buttonClick(sender:)), for: .touchUpInside)
}
}
}
ParticipateButton
class ParticipateButton: UIButton {
var typeOfButton: participateLists!
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = LebensfitSettings.Colors.buttonBG
self.tintColor = LebensfitSettings.Colors.basicTintColor
self.imageView?.contentMode = .scaleAspectFit
self.contentEdgeInsets = UIEdgeInsets(top: 7, left: 0, bottom: 7, right: 0)
}
convenience init(type: participateLists) {
self.init()
self.typeOfButton = type
setImage(type: type)
}
func setImage(type: participateLists) {
var buttonImage: UIImage?
switch type {
case .sure:
buttonImage = UIImage(named: "pb_sure")?.withRenderingMode(.alwaysTemplate)
[...]
}
self.setImage(buttonImage, for: .normal)
}
}
This is the error that gets thrown:
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x600001adb020 'UISV-spacing' H: [LebensfitFirebase.ParticipateButton:0x7f9899c5eaa0]-(1)-[LebensfitFirebase.ParticipateButton:0x7f9899c5fad0] (active)>
Edit:
participateButtonStackView.leftAnchor = view.leftAnchor
participateButtonStackView.rightAnchor = view.rightAnchor
participateButtonStackView.bottomAnchor = view.bottomAnchor
participateButtonStackView.heightAnchor = equalToConstant(50)
I was not able to solve this problem so i built this workaround:
ParticipateButton
func setBordersLeftRight() {
let borderLeft = UIView()
let borderRight = UIView()
borderLeft.backgroundColor = .gray
borderRight.backgroundColor = .gray
addSubview(borderLeft)
addSubview(borderRight)
borderLeft.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0.5, height: 0)
borderRight.anchor(top: topAnchor, left: nil, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0.5, height: 0)
}

Having issues with activating constraints programmatically and getting errors

Last night I posted a question on here where I used storyboards to set the constraints. I decided to test it with anchors and do constraints programmatically.
I am getting this error:
Terminating app due to uncaught exception 'NSGenericException',
reason: 'Unable to activate constraint with anchors
and
because they have no common ancestor. Does the constraint or its
anchors reference items in different view hierarchies? That's
illegal.'
I know this has to do with views not being a subview of a certain other view however, I have checked it plenty of times and I don't know what to do.
here is the code:
import UIKit
import CoreData
class editViewController: UIViewController {
let screenSize: CGRect = UIScreen.main.bounds
let moContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var editnotes: addednotes?
let mainbackgroundview: UIView =
{
let view = UIView()
view.backgroundColor = .black
view.alpha = 0.3
return view
}()
let backgroundview1: UIView =
{
let view = UIView()
view.backgroundColor = .purple
return view
}()
let edittasktextview1: UITextView = {
let tv = UITextView()
tv.backgroundColor = .black
return tv
}()
let cancelButton : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Cancel", for: .normal)
button.addTarget(self, action: #selector(handleCancelButton), for:
.touchUpInside)
return button
}()
let doneButton1 : UIButton = {
let button = UIButton(type: .system)
button.setTitle("Done", for: .normal)
button.addTarget(self, action: #selector(handleDoneButton), for: .touchUpInside)
return button
}()
#objc func handleCancelButton() {
edittasktextview1.endEditing(true)
dismiss(animated: true, completion: nil)
}
#objc func handleDoneButton()
{
dismiss(animated: true, completion: nil)
if edittasktextview1.text.isEmpty == true
{
moContext.delete(editnotes!)
}
else
{
editnotes?.sNote = edittasktextview1.text
}
var error: NSError?
do {
// Save The object
try moContext.save()
print("SAVED")
} catch let error1 as NSError {
error = error1
}
edittasktextview1.endEditing(true)
}
override func viewWillDisappear(_ animated: Bool) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NotificationID"), object: nil)
}
override func viewDidAppear(_ animated: Bool) {
//add something here
// edittasktextview.becomeFirstResponder()
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.tintColor = UIColor.white
navigationItem.title = "Edit Tasks"
edittasktextview1.text = editnotes?.sNote
backgroundview1.backgroundColor = editnotes?.sPriorityColor
edittasktextview1.backgroundColor = .clear
setupviews()
//adding gesture to the background view to dismiss keyboard
let dismisskeyboardgesture = UITapGestureRecognizer()
dismisskeyboardgesture.addTarget(self, action: #selector(dismisskeyboard))
let maskPath = UIBezierPath.init(roundedRect: self.backgroundview1.bounds, byRoundingCorners:[.topLeft, .topRight], cornerRadii: CGSize.init(width: 20.0, height: 0))
let maskLayer = CAShapeLayer()
maskLayer.frame = self.backgroundview1.bounds
maskLayer.path = maskPath.cgPath
self.backgroundview1.layer.mask = maskLayer
view.backgroundColor = .clear
//view.addGestureRecognizer(dismisskeyboardgesture)
view.addSubview(mainbackgroundview)
mainbackgroundview.addSubview(backgroundview1)
// view.addSubview(backgroundview1)
backgroundview1.addSubview(edittasktextview1)
backgroundview1.addSubview(cancelButton)
backgroundview1.addSubview(doneButton1)
}
#objc func dismisskeyboard()
{
edittasktextview1.endEditing(true)
}
func setupviews()
{
mainbackgroundview.anchorToTop(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor)
backgroundview1.anchor(view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, topConstant: 45, leftConstant: 0, bottomConstant: 0, rightConstant: 0, widthConstant: 0, heightConstant: 0)
edittasktextview1.anchor(view.topAnchor, left: view.leftAnchor, bottom: nil, right: view.rightAnchor, topConstant: 40, leftConstant: 8, bottomConstant: 0, rightConstant: 8, widthConstant: 0, heightConstant: 150)
cancelButton.anchor(edittasktextview1.bottomAnchor, left: nil, bottom: nil, right: view.rightAnchor, topConstant: 18, leftConstant: 0, bottomConstant: 0, rightConstant: 8, widthConstant: 40, heightConstant: 30)
doneButton1.anchor(edittasktextview1.bottomAnchor, left: view.leftAnchor, bottom: nil, right: nil, topConstant: 18, leftConstant: 8, bottomConstant: 0, rightConstant: 0, widthConstant: 40, heightConstant: 30)
}
}
Help will be appreciated, as I am stuck at this problem for nearly 18 hours :(
UPDATE #1
Not getting the same error now, however, I am not able to set the backgroundview1 on the mainbackgroundview.
I am adding it as a subview:
mainbackgroundview.addSubview(backgroundview1)
In setupviews() I am placing the constraint like so:
NSLayoutConstraint.activate([
backgroundview1.leftAnchor.constraint(equalTo: mainbackgroundview.leftAnchor, constant: 0),
backgroundview1.rightAnchor.constraint(equalTo: mainbackgroundview.rightAnchor, constant: 0),
backgroundview1.bottomAnchor.constraint(equalTo: mainbackgroundview.bottomAnchor, constant: 0),
backgroundview1.topAnchor.constraint(equalTo: mainbackgroundview.topAnchor, constant: 45)
])
Still I am not getting the backgroundview1 to be placed on the mainbackgroundview. The mainbackgroundview is showing up though on the simulator.
I am adding mainbackgroundview like so:
NSLayoutConstraint.activate([
mainbackgroundview.leftAnchor.constraint(equalTo: view.leftAnchor),
mainbackgroundview.rightAnchor.constraint(equalTo: view.rightAnchor),
mainbackgroundview.topAnchor.constraint(equalTo: view.topAnchor),
mainbackgroundview.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
What am I doing wrong now?
In setupViews() function you are adding constraints but not setting them as active. This is a sample anchor constraint that can be added successfully:
myView.bottomAnchor.constraint(equalTo: view.topAnchor,
constant: 8).isActive=true
Try adding .isActive at the end of each of your constraint.
Moreover you can add relevant anchor constraints for example: topAnchor, bottomAnchor, widthAnchor, heightAnchor, leadingAnchor etc. Try simplifying your constraints.

Resources