Why label does not fit inside tableviewcell? - uitableview

I am using SnapKit for autolaoyut.
This is my view:
private func makeIconUI() {
contentView.addSubview(iconView)
iconView.snp.makeConstraints { (make) in
make.leading.equalToSuperview().inset(Dimesion.sidePadding)
make.height.width.equalTo(19)
make.top.equalToSuperview().inset(24)
}
}
private func makeNumberUI() {
contentView.addSubview(numberLabel)
numberLabel.backgroundColor = .yellow
numberLabel.snp.makeConstraints { (make) in
make.leading.equalTo(iconView.snp.trailing)
make.top.equalToSuperview().inset(24)
make.bottom.equalToSuperview()
}
}
private func makeTitleUI() {
contentView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { (make) in
make.leading.equalTo(numberLabel.snp.trailing)
make.trailing.equalTo(self.snp.trailing)
make.top.equalToSuperview()
}
}
The problem is:
Yellow label does not fit fully. The second label pushing on it, but I can fix it.

Am confused.Yellow doesn't fit fully like top of both labels dont match? then remove inset
private func makeNumberUI() {
contentView.addSubview(numberLabel)
numberLabel.backgroundColor = .yellow
numberLabel.snp.makeConstraints { (make) in
make.leading.equalTo(iconView.snp.trailing)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
}
if logic is horizontally then give it a width like:
view.addSubview(numberLabel)
numberLabel.backgroundColor = .yellow
numberLabel.snp.makeConstraints { (make) in
make.leading.equalTo(iconView.snp.trailing)
make.width.equalTo(30)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}

Related

Access view file from UICollectionViewListCell

I have a question about the UICollection view list's separatorLayoutGuide. I saw this article and understood I need to override the function updateConstraints() in order to update the separator layout guide.
like this...
override func updateConstraints() {
super.updateConstraints()
separatorLayoutGuide.leadingAnchor.constraint(equalTo: otherView.leadingAnchor, constant: 0.0).isActive = true
}
I can see the tiny space between the cell's leading anchor and seprateguide's leading anchor like the image below and I want to fix it. (like the left side of the cell)
The problem is, however, I created a custom collection view list cell using this article and cannot change the separatorLayoutGuide leading to the custom view's leading.
I added the customListCell.separatorLayoutGuide.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true in order to position the leading of separatorLayoutGuide to the customView's leading, and I get the
"UILayoutGuide:0x2822d8b60'UICollectionViewListCellSeparatorLayoutGuide'.leading"> and <NSLayoutXAxisAnchor:0x280e9cac0 "ContentView:0x15960db90.leading"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal.'
error.
After I've done the research, I figured I didn't addSubview for the separatorLayoutGuide, but even if I add a subview to the custom view, the app crashes. Is there a way to change the separator guide's leading anchor when using the custom UIView?
class CustomListCell: UICollectionViewListCell {
var item: TestItem?
override func updateConfiguration(using state: UICellConfigurationState) {
// Create new configuration object
var newConfiguration = ContentConfiguration().updated(for: state)
newConfiguration.name = item.name
newConfiguration.state = item.state
// Set content configuration
contentConfiguration = newConfiguration
}
}
struct ContentConfiguration: UIContentConfiguration, Hashable {
var name: String?
var state: String?
func makeContentView() -> UIView & UIContentView {
return ContentView(configuration: self)
}
func updated(for state: UIConfigurationState) -> Self {
guard let state = state as? UICellConfigurationState else {
return self
}
// Updater self based on the current state
let updatedConfiguration = self
if state.isSelected {
print("is selected")
} else {
print("is deselected")
}
return updatedConfiguration
}
}
class ContentView: UIView, UIContentView {
let contentsView = UIView()
let customListCell = CustomListCell()
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = ""
return label
}()
lazy var statusLabel: UILabel = {
let label = UILabel()
label.text = ""
return label
}()
lazy var symbolImageView: UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
return imageView
}()
init(configuration: ContentConfiguration) {
// Custom initializer implementation here.
super.init(frame: .zero)
setupAllViews()
apply(configuration: configuration)
}
override func updateConstraints() {
super.updateConstraints()
customListCell.separatorLayoutGuide.leadingAnchor.constraint(equalTo: self.leadingAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var currentConfiguration: ContentConfiguration!
var configuration: UIContentConfiguration {
get {
currentConfiguration
}
set {
guard let newConfiguration = newValue as? ContentConfiguration else {
return
}
apply(configuration: newConfiguration)
}
}
func setupAllViews() {
// add subviews and add constraints
}
func apply(configuration: ContentConfiguration) {
}
}
by overriding updateConstraints in a UICollectionViewListCell subclass
In your code you have a customListCell instance variable which is not necessary. Also you should not add separatorLayoutGuide as a subview anywhere.
Try to access the cell's contentView:
override func updateConstraints() {
super.updateConstraints()
if let customView = cell.contentView as? ContentView {
separatorLayoutGuide.leadingAnchor.constraint(equalTo: customView.leadingAnchor, constant: 0.0).isActive = true
}
}
Like this you can access any subview in your ContentView.
Another question is: Is it necessary to create a new constraint every time updateConstraints is called? Are constraints from previous calls still there? According to the documentation of updateConstraints:
Your implementation must be as efficient as possible. Do not deactivate all your constraints, then reactivate the ones you need. Instead, your app must have some way of tracking your constraints, and validating them during each update pass. Only change items that need to be changed. During each update pass, you must ensure that you have the appropriate constraints for the app’s current state.
Therefore I suggest this approach:
class ContentView: UIView, UIContentView {
var separatorConstraint: NSLayoutConstraint?
func updateForCell(_ cell: CustomListCell) {
if separatorConstraint == nil {
separatorConstraint = cell.separatorLayoutGuide.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 30)
}
separatorConstraint?.isActive = true
}
}
class CustomListCell: UICollectionViewListCell {
...
override func updateConstraints() {
super.updateConstraints()
(contentView as? AlbumContentView)?.updateForCell(self)
}
}

Self sizing in Custom subclass of UICollectionViewListCell not working in iOS 14

Background:
iOS14 has introduced a new way to register and configure collectionViewCell and In Lists In CollectionView WWDC video apple developer says that self sizing is default to UICollectionViewListCell and we don't have to explicitly specify the height for cells. This works great if I use system list cell in various configurations but self sizing fails when I use it with custom subclass of UICollectionViewListCell
What have I tried?
iOS 14 has introduced a new way to configure the cells, where we don't access the cells components directly to set the various UI properties rater we use content configuration and background configuration to update/configure cells. This becomes little tricky when we use custom cells.
CustomSkillListCollectionViewCell
class CustomSkillListCollectionViewCell: UICollectionViewListCell {
var skillLavel: String? {
didSet {
setNeedsUpdateConfiguration()
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func updateConfiguration(using state: UICellConfigurationState) {
backgroundConfiguration = SkillListViewBackgroundConfiguration.getBackgroundConfiguration(for: state)
var content = SkillListViewContentConfiguration().updated(for: state)
content.label = skillLavel
contentConfiguration = content
}
}
SkillListViewBackgroundConfiguration
struct SkillListViewBackgroundConfiguration {
#available(iOS 14.0, *)
static func getBackgroundConfiguration(for state: UICellConfigurationState) -> UIBackgroundConfiguration {
var background = UIBackgroundConfiguration.clear()
if state.isHighlighted || state.isSelected {
background.backgroundColor = UIColor.green.withAlphaComponent(0.4)
}
else if state.isExpanded {
background.backgroundColor = UIColor.red.withAlphaComponent(0.5)
}
else {
background.backgroundColor = UIColor.red.withAlphaComponent(0.9)
}
return background
}
}
SkillListViewContentConfiguration
struct SkillListViewContentConfiguration: UIContentConfiguration {
var label: String? = nil
#available(iOS 14.0, *)
func makeContentView() -> UIView & UIContentView {
return SkillListView(contentConfiguration: self)
}
#available(iOS 14.0, *)
func updated(for state: UIConfigurationState) -> Self {
guard let state = state as? UICellConfigurationState else {
return self
}
let updatedConfig = self
return updatedConfig
}
}
Finally subview SkillListView
class SkillListView: UIView, UIContentView {
var configuration: UIContentConfiguration {
get {
return self.appliedConfiguration
}
set {
guard let newConfig = newValue as? SkillListViewContentConfiguration else { return }
self.appliedConfiguration = newConfig
apply()
}
}
private var appliedConfiguration: SkillListViewContentConfiguration!
var skillNameLabel: UILabel!
#available(iOS 14.0, *)
init(contentConfiguration: UIContentConfiguration) {
super.init(frame: .zero)
self.setUpUI()
self.configuration = contentConfiguration
self.apply()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func apply() {
self.skillNameLabel.text = self.appliedConfiguration.label
}
private func setUpUI() {
self.skillNameLabel = UILabel(frame: .zero)
skillNameLabel.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
skillNameLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
self.skillNameLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(skillNameLabel)
NSLayoutConstraint.activate([
self.skillNameLabel.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 20),
self.skillNameLabel.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: 20),
self.skillNameLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor, constant: 20)
])
}
}
And I configure it using
let skillsCellConfigurator = UICollectionView.CellRegistration<CustomSkillListCollectionViewCell, Employee> { (cell, indexPath, employee) in
cell.skillLavel = employee.individualSkil
cell.accessories = [.disclosureIndicator()]
}
Issue:
Everything else works great except height
First, you need at least 1 more constraint to guarantee satisfaction as top, bottom, and leading needs a trailing or centerX to go with them. But more so, it is probably constraining to the margins rather than the superview. Cells should automatically respect margins like safe area as long as the UICollectionView itself respects them which I think is default behavior. The layoutmarginguide for cells is significantly pushed down on the top, which can be seen from a dummy example in interfacebuilder if you check.

UIStackView arranged sub view bottom arrangement problem

I want to start addArrangedSubview from bottom alignment as like as attached 2nd screenshot(3rd is my actual working screenshot). But every time it arranged from top to bottom. But I need it from bottom to top arrange. I want to create this design using UIStackView inside UIScrollView. I'm trying it with UIScrollView because of landscapes orientation support. And UIStackView for better efficiency with native view.
https://github.com/amitcse6/BottomAlignStackView
import UIKit
import SnapKit
class ViewController: UIViewController {
private var storeBack: UIView?
private var myImageView: UIImageView?
private var myScrollView: UIScrollView?
private var myStackView: UIStackView?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor=UIColor.white
self.loadStoreBack()
self.loadBackgroundImage()
self.loadScrollView()
self.loadStackView()
self.loadUI()
}
func loadStoreBack() {
self.storeBack=UIView()
self.view.addSubview(self.storeBack!)
self.storeBack?.backgroundColor=UIColor.white
self.storeBack?.snp.remakeConstraints { (make) in
make.top.equalTo(self.view!.snp.top)
make.left.equalTo(self.view.snp.left)
make.right.equalTo(self.view.snp.right)
make.bottom.equalTo(self.view.snp.bottom)
}
}
func loadBackgroundImage() -> Void {
self.myImageView = UIImageView()
self.storeBack?.addSubview(self.myImageView!)
self.myImageView?.contentMode = .scaleAspectFill
self.myImageView?.image=UIImage(named: "welcome-background")
self.myImageView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.storeBack!.snp.top)
make.left.equalTo(self.storeBack!.snp.left)
make.right.equalTo(self.storeBack!.snp.right)
make.bottom.equalTo(self.storeBack!.snp.bottom)
}
}
func loadScrollView() {
self.myScrollView = UIScrollView()
self.storeBack?.addSubview(self.myScrollView!)
self.myScrollView?.backgroundColor=UIColor.clear
self.myScrollView?.showsHorizontalScrollIndicator = false
self.myScrollView?.showsVerticalScrollIndicator = false
self.myScrollView?.bounces=false
self.myScrollView?.isScrollEnabled=true
self.myScrollView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.storeBack!.snp.top)
make.left.equalTo(self.storeBack!.snp.left)
make.right.equalTo(self.storeBack!.snp.right)
make.bottom.equalTo(self.storeBack!.snp.bottom)
make.width.equalTo(self.storeBack!)
make.height.equalTo(self.storeBack!)
}
}
func loadStackView() {
self.myStackView = UIStackView()
self.myScrollView?.addSubview(self.myStackView!)
self.myStackView?.backgroundColor=UIColor.clear
self.myStackView?.axis = .vertical
self.myStackView?.spacing = 0
//self.myStackView?.alignment = .bottom
self.myStackView?.snp.remakeConstraints { (make) in
make.top.equalTo(self.myScrollView!.snp.top)
make.left.equalTo(self.myScrollView!.snp.left)
make.right.equalTo(self.myScrollView!.snp.right)
make.bottom.equalTo(self.myScrollView!.snp.bottom)
make.width.equalTo(self.myScrollView!)
make.height.equalTo(self.myScrollView!).priority(250)
}
}
func loadUI() {
for n in 0..<5 {
if n%2 == 0 {
loadBuddyLogoImageView1()
}else{
loadBuddyLogoImageView2()
}
}
}
func loadBuddyLogoImageView1() {
let subBackView=UIView()
self.myStackView?.addArrangedSubview(subBackView)
subBackView.backgroundColor=UIColor.clear
let backgroundView=UIView()
subBackView.addSubview(backgroundView)
backgroundView.backgroundColor=UIColor.red
backgroundView.snp.remakeConstraints { (make) in
make.top.equalTo(subBackView.snp.top)
make.left.equalTo(subBackView.snp.left)
make.right.equalTo(subBackView.snp.right)
make.bottom.equalTo(subBackView.snp.bottom)
make.height.equalTo(100)
}
}
func loadBuddyLogoImageView2() {
let subBackView=UIView()
self.myStackView?.addArrangedSubview(subBackView)
subBackView.backgroundColor=UIColor.clear
let backgroundView=UIView()
subBackView.addSubview(backgroundView)
backgroundView.backgroundColor=UIColor.green
backgroundView.snp.remakeConstraints { (make) in
make.top.equalTo(subBackView.snp.top)
make.left.equalTo(subBackView.snp.left)
make.right.equalTo(subBackView.snp.right)
make.bottom.equalTo(subBackView.snp.bottom)
make.height.equalTo(100)
}
}
}

AutoLayout: multiline label and fixed-size button

I'm trying to replicate the following layout from the Kayak app:
The layout consists of UICollectionViewCell with a UILabel and a INUIAddVoiceShortcutButton.
However, in my implementation the label doesn't force the cell to stretch further when the text doesn't fit:
How could I make the UICollectionViewCell grow with the label, and not truncate the label to the size of the cell?
The whole code for the cell:
final class AddToSiriCell: CornerMaskCellBase {
lazy var button: INUIAddVoiceShortcutButton = {
let b = INUIAddVoiceShortcutButton(style: .whiteOutline)
return b
}()
lazy var textLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
configureViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configureViews() {
textLabel.text = "View balance with your pre-recorded Siri Command .View balance with your pre-recorded Siri Command View balance with your pre-recorded Siri Command View balance with your pre-recorded Siri Command "
contentView.backgroundColor = .white
[button, textLabel].forEach(contentView.addSubview)
button.snp.makeConstraints { (make) in
make.top.bottom.trailing.equalTo(contentView.layoutMarginsGuide)
}
textLabel.snp.makeConstraints { (make) in
make.top.bottom.leading.equalTo(contentView.layoutMarginsGuide).priority(.required)
make.trailing.equalTo(button.snp.leading).priority(.required)
}
}
}
Update 1: Added "Base Class" with fixed width
Here is the base class I use for all the cells in the UICollectionView:
import UIKit
import SnapKit
class AutoSizingCellBase: UICollectionViewCell {
override class var requiresConstraintBasedLayout: Bool {
return true
}
private final var widthConstraint: Constraint?
override init(frame: CGRect) {
super.init(frame: frame)
contentView.layoutMargins = UIEdgeInsets(padding: 14)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func updateConstraints() {
if widthConstraint == nil {
if let window = window {
let width = window.bounds.width - 16
contentView.snp.makeConstraints { (make) in
widthConstraint = make.width.equalTo(width).priority(.required).constraint
}
}
contentView.translatesAutoresizingMaskIntoConstraints = true
}
super.updateConstraints()
}
}
Set your top constraint on both the label and the button to greaterThanOrEqual
Set your bottom constraint on both the label and the button to lessThanOrEqual
Edit:
Both should also have centerY constraints.
Here is a complete example (I'm not on iOS 12, so I used a standard UIButton in place of INUIAddVoiceShortcutButton). I also set the background of the label to cyan to make it easy to see its resulting frame:
//
// SnapTableViewController.swift
//
// Created by Don Mag on 10/19/18.
//
import UIKit
class SnapCell: UITableViewCell {
lazy var theButton: UIButton = {
let b = UIButton()
b.backgroundColor = .yellow
b.setTitle("Add to Siri", for: .normal)
b.setTitleColor(.black, for: .normal)
b.layer.cornerRadius = 8
b.layer.borderColor = UIColor.black.cgColor
b.layer.borderWidth = 1
return b
}()
lazy var theLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.backgroundColor = .cyan
return label
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureViews()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureViews()
}
func configureViews() -> Void {
contentView.backgroundColor = .white
[theButton, theLabel].forEach(contentView.addSubview)
// constrain button size to 120 x 40
theButton.snp.makeConstraints { (make) in
make.width.equalTo(120)
make.height.equalTo(40)
}
// constrain button to trailing margin
theButton.snp.makeConstraints { (make) in
make.trailing.equalTo(contentView.layoutMarginsGuide)
}
// constrain button top to greaterThanOrEqualTo margin
theButton.snp.makeConstraints { (make) in
make.top.greaterThanOrEqualTo(contentView.layoutMarginsGuide)
}
// constrain button bottom to lessThanOrEqualTo margin
theButton.snp.makeConstraints { (make) in
make.bottom.lessThanOrEqualTo(contentView.layoutMarginsGuide)
}
// also constrain button to centerY
theButton.snp.makeConstraints { (make) in
make.centerY.equalTo(contentView.snp.centerY)
}
// constrain label to leading margin
theLabel.snp.makeConstraints { (make) in
make.leading.equalTo(contentView.layoutMarginsGuide)
}
// constrain label top to greaterThanOrEqualTo margin
theLabel.snp.makeConstraints { (make) in
make.top.greaterThanOrEqualTo(contentView.layoutMarginsGuide)
}
// constrain label bottom to lessThanOrEqualTo margin
theLabel.snp.makeConstraints { (make) in
make.bottom.lessThanOrEqualTo(contentView.layoutMarginsGuide)
}
// also constrain label to centerY
theLabel.snp.makeConstraints { (make) in
make.centerY.equalTo(contentView.snp.centerY)
}
// constrain label trailing to 8-pts from button leading
theLabel.snp.makeConstraints { (make) in
make.trailing.equalTo(theButton.snp.leading).offset(-8)
}
}
}
class SnapTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SnapCell", for: indexPath) as! SnapCell
switch indexPath.row % 4 {
case 0:
cell.theLabel.text = "One line label."
case 1:
cell.theLabel.text = "This label has\nTwo Lines."
case 2:
cell.theLabel.text = "This label has enough text that is will wrap to Three Lines (on an iPhone 7)."
default:
cell.theLabel.text = "View balance with your pre-recorded Siri Command .View balance with your pre-recorded Siri Command View balance with your pre-recorded Siri Command View balance with your pre-recorded Siri Command "
}
return cell
}
}
Set the label's top, bottom and left constraints with superview i.e cell's content view. Now give your button to fixed height and width constraints and provide top, left and right margins, left margin should be with your label. Now set your label's number of lines property as zero. Any doubt please comment.

Custom font MKAnnotationView resize animation

I have achieved to set a custom font and color for my callout using this solution, but it produces a strange animation because it first sets the size according to the previous font and then resizes the box with the new one check this:
Code to change font and color
#objc class CustomAnnotationView: MKAnnotationView {
override func didAddSubview(_ subview: UIView) {
if isSelected {
setNeedsLayout()
}
}
override func layoutSubviews() {
if !isSelected {
return
}
loopViewHierarchy { (view: UIView) -> Bool in
if let label = view as? UILabel {
label.font = ViewUtil.fontMediumWithSize(14)
label.textColor = ViewUtil.BlueGray
return false
}
return true
}
super.layoutSubviews()
}
}
typealias ViewBlock = (_ view: UIView) -> Bool
extension UIView {
func loopViewHierarchy(block: ViewBlock?) {
if block?(self) ?? true {
for subview in subviews {
subview.loopViewHierarchy(block: block)
}
}
}
}

Resources