Resizing image inside of UIButton - ios

I have been trying to get the system images I placed inside of a UIButton to scale aspect fit, but no solutions I find online seem to work. This is what I have tried so far:
//the cells objects
var acceptButton = UIButton(type: .custom)
var declineButton = UIButton(type: .custom)
override init(frame: CGRect) {
super.init(frame: frame)
//add the contents and set its autolayout constraints
setAutoLayoutConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//function to set the autolayout constraints for the cell's contents
func setAutoLayoutConstraints() {
//create the stack view which will contain the accept and reject buttons
let buttonStackView = UIStackView()
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
buttonStackView.alignment = .fill
//add the buttons to the stack view
buttonStackView.addArrangedSubview(acceptButton)
buttonStackView.addArrangedSubview(declineButton)
//add the button stack view to the cell
self.addSubview(buttonStackView)
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
buttonStackView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
buttonStackView.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
buttonStackView.leadingAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor).isActive = true
//add the button images
let addImage = UIImage(systemName: "checkmark.circle")
acceptButton.imageView?.contentMode = .scaleAspectFit
acceptButton.setImage(addImage, for: .normal)
acceptButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
acceptButton.addTarget(self, action: #selector(addButtonClicked), for: .touchUpInside)
let declineImage = UIImage(systemName: "xmark.octagon")
declineButton.imageView?.contentMode = .scaleAspectFit
declineButton.setImage(declineImage, for: .normal)
declineButton.addTarget(self, action: #selector(declineButtonClicked), for: .touchUpInside)
}
This is what I get:
(The "test test" label is just what I added for the left half of the cell's contents, can be ignored)
I have tried multiple solutions such as setting the edge insets, setting the content mode of the button and the button's image view, using a custom button, etc. but I can't seem to get the image to resized.
NOTE: When I make the image the background image with setBackgroundImage, I am able to resize the button but I can not scale aspect fit it, only fill it horizontally and vertically which is not what I am looking to do.
Any help is appreciated, thanks!!

This is a simple solution example: add first your labelTest and after that set stackView leading anchor to labelTest trailing...
this is my tableView:
class YourViewController: UIViewController, UITabBarDelegate, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .green
tableView.delegate = self
tableView.dataSource = self
tableView.register(myCell.self, forCellReuseIdentifier: "cellId")
tableView.backgroundColor = .white
tableView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! myCell
return cell
}
}
Now set my cell with arrangedSubviews and set constraints to content view (this is the correct way, not directly to cell):
class myCell: UITableViewCell {
let labelTest: UILabel = {
let label = UILabel()
label.text = "test Label"
label.backgroundColor = .red
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
var acceptButton = UIButton(type: .custom)
var declineButton = UIButton(type: .custom)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
acceptButton.backgroundColor = .purple
declineButton.backgroundColor = .brown
setAutoLayoutConstraints()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//function to set the autolayout constraints for the cell's contents
func setAutoLayoutConstraints() {
contentView.addSubview(labelTest)
labelTest.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
labelTest.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
labelTest.trailingAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
labelTest.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
let buttonStackView = UIStackView(arrangedSubviews: [acceptButton, declineButton])
buttonStackView.axis = .horizontal
buttonStackView.distribution = .fillEqually
buttonStackView.translatesAutoresizingMaskIntoConstraints = false
//add the button stack view to the cell
contentView.addSubview(buttonStackView)
buttonStackView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
buttonStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
buttonStackView.leadingAnchor.constraint(equalTo: labelTest.trailingAnchor).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
//add the buttons images
let addImage = UIImage(systemName: "checkmark.circle")
acceptButton.imageView?.contentMode = .scaleAspectFit
acceptButton.setImage(addImage, for: .normal)
acceptButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
acceptButton.addTarget(self, action: #selector(addButtonClicked), for: .touchUpInside)
let declineImage = UIImage(systemName: "xmark.octagon")
declineButton.imageView?.contentMode = .scaleAspectFit
declineButton.setImage(declineImage, for: .normal)
declineButton.addTarget(self, action: #selector(declineButtonClicked), for: .touchUpInside)
}
}
If you want some space to left/right simply add constant to relative constraint:
labelTest.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
buttonStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
This is the results

Related

Use UIStackView as UITableViewCell's accessoryView

I would like my table view cells to have multiple accessory buttons. To achieve this, I decided to add them to stack views:
extension MyTableViewController {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
cell.textLabel?.text = "Test"
cell.accessoryView = self.newStackView()
return cell
}
private func newStackView() -> UIView {
let eyeButton = UIButton(image: "eye")
let refreshButton = UIButton(image: "refresh")
let stackView = UIStackView(arrangedSubviews: [eyeButton, refreshButton])
stackView.axis = .horizontal
return stackView
}
}
fileprivate extension UIButton {
convenience init(image systemName: String) {
self.init()
self.setImage(UIImage(systemName: systemName)!, for: .normal)
self.sizeToFit()
}
}
However, the buttons do not appear.
If I use one of the buttons as the accessory view, it will in fact appear:
cell.accessoryView = UIButton(image: "eye")
Is there something wrong with the way I create the stack views?
You need to set “frame” constraints to your views
For example
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.topAnchor),
stackView.leftAnchor.constraint(equalTo: view.leftAnchor),
stackView.rightAnchor.constraint(equalTo: view.rightAnchor),
stackView.heightAnchor.constraint(equalToConstant: 10l)
])
Or
stackview.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
Follow this question
Your stackView should tell it's size
private func newStackView() -> UIView {
let eyeButton = UIButton(image: "eye")
let refreshButton = UIButton(image: "refresh")
let stackView = UIStackView(arrangedSubviews: [eyeButton, refreshButton])
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = 5
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.widthAnchor.constraint(equalToConstant: 65),
stackView.heightAnchor.constraint(equalToConstant: 30)
])
return stackView
}

How to add options menu on UIBarButtonItem in swift?

I am trying to add options menu over a UIBarButtonItem similar to what we see in android for material design. Similar to this
But I am stuck on how to add it over a UIBarButtonItem. Once clicked this menu should pop up anchored to that UIBarButtonItem and clicking outside should dismiss it. How to achieve the same?
class ABMenu: UIView {
private var items:[ABMenuItem] = []
private var tableView:UITableView!
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(barButtonItem:UIBarButtonItem,items:[ABMenuItem]) {
self.init(frame: .zero)
self.items = items
self.commonInit(barButtonItem: barButtonItem)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func commonInit(barButtonItem:UIBarButtonItem){
self.frame = CGRect(x: 10, y: 20, width: 200, height: self.items.count * 40)
self.backgroundColor = .white
self.layer.cornerRadius = 5
self.layer.shadowColor = UIColor.gray.withAlphaComponent(0.7).cgColor
self.layer.shadowOffset = CGSize(width: -1.0, height: 1.0)
self.layer.shadowRadius = 0.8
self.layer.shadowOpacity = 0.5
tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints=false
tableView.backgroundColor = .clear
tableView.tableFooterView=UIView()
tableView.separatorStyle = .none
tableView.delegate = self
tableView.dataSource = self
tableView.register(ABMenuItemCell.self, forCellReuseIdentifier: "itemCell")
self.addSubview(tableView)
tableView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
tableView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
tableView.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
tableView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
}
}
extension ABMenu:UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! ABMenuItemCell
cell.setup(item:self.items[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.items[indexPath.row].onTap()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
}
class ABMenuItemCell:UITableViewCell {
private var itemLabel:UILabel!
func setup(item:ABMenuItem){
for view in self.contentView.subviews {
view.removeFromSuperview()
}
self.selectionStyle = .none
itemLabel = UILabel()
itemLabel.translatesAutoresizingMaskIntoConstraints=false
itemLabel.backgroundColor = .white
itemLabel.text = item.name
itemLabel.textColor = .black
itemLabel.font = .systemFont(ofSize: 15)
itemLabel.numberOfLines = 1
itemLabel.lineBreakMode = .byTruncatingMiddle
self.contentView.addSubview(itemLabel)
itemLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor, constant: 0).isActive = true
itemLabel.leftAnchor.constraint(equalTo: self.contentView.leftAnchor, constant: 10).isActive = true
itemLabel.rightAnchor.constraint(equalTo: self.contentView.rightAnchor, constant: -10).isActive = true
itemLabel.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
}
struct ABMenuItem {
var name:String
var onTap:(()->Void)
}
Try creating a view for the menu, then setting the X and Y position relative to the button, and setting the view's layer.zPosition = 1
Below is an example of how this could be done. Note, I haven't tested this code yet. To implement clicking outside the menu to close it, you'll need to create a transparent view the size of the screen, positioned underneath the menu, add a UITapGestureRecognizer to it, and then link to an IBAction that removes the menu from the view.
// calculate location of menu
let openMenuButtonOrigin = openMenuButton.frame.origin
let menuWidth = 200
let menuHeight = 100
let menuX = openMenuButtonOrigin.x - menuWidth
let menuY = openMenuButtonOrigin.y + menuHeight
// create menu (this can be done in XIB/Storyboard file as well)
let menuView = UIView(frame: CGRect(x: menuX, y: menuY, width: menuWidth, height: menuHeight))
// create menu option
let shareOption = UILabel()
shareOption.text = "Share"
shareOption.topAnchor.constraint(equalTo: menuView).isActive = true
shareOption.bottomAnchor.constraint(equalTo: menuView).isActive = true
shareOption.leadingAnchor.constraint(equalTo: menuView).isActive = true
shareOption.trailingAnchor.constraint(equalTo: menuView).isActive = true
menuView.addSubview(shareOption)
// add menu to view
menuView.layer.zPosition = 1
view.addSubview(menuView)

Align Button To Left Side Of UITableView Cell Programmatically Swift

I'm creating a simple app using Swift 5, Xcode 11, and a ui table view controller. Inside the ui table view, I want 2 buttons: One button on the left of my table view, the other on the right. I have tried many other related/similar question's answers, but all of them failed(probably because 1. Too Old, 2. Answer written in OBJ-C).
Here's my Table View Controller:
import UIKit
#objcMembers class CustomViewController: UITableViewController {
var tag = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
// 3
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tag = tag + 1
let cell = tableView.dequeueReusableCell(withIdentifier: "themeCell", for: indexPath) as! ThemeCell
var cellButton: UIButton!
cellButton = UIButton(frame: CGRect(x: 5, y: 5, width: 50, height: 30))
cell.addSubview(cellButton)
cell.img.image = UIImage(named: SingletonViewController.themes[indexPath.row])
cell.accessoryView = cellButton
cellButton.backgroundColor = UIColor.red
cellButton.tag = tag
return cell
}
}
Here's what I'm currently getting:
add below code for apply constraint worked
let cellButton = UIButton(frame: CGRect.zero)
cellButton.translatesAutoresizingMaskIntoConstraints = false
cell.addSubview(cellButton)
cell.accessoryView = cellButton
cellButton.backgroundColor = UIColor.red
cellButton.leadingAnchor.constraint(equalTo: cell.leadingAnchor, constant: 5).isActive = true
cellButton.topAnchor.constraint(equalTo: cell.topAnchor, constant: 5).isActive = true
cellButton.widthAnchor.constraint(equalToConstant: 50).isActive = true
cellButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
You can achieve right and left align button from constraints.
Below is my code to align view right or left.
override func viewDidLoad() {
let leftButton = UIButton(type: .custom)
leftButton.backgroundColor = UIColor.red
self.view.addSubview(leftButton)
leftButton.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = leftButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20)
let verticalConstraint = leftButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraint = leftButton.widthAnchor.constraint(equalToConstant: 100)
let heightConstraint = leftButton.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
let rightButton = UIButton(type: .custom)
rightButton.backgroundColor = UIColor.red
self.view.addSubview(rightButton)
rightButton.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraintRight = rightButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
let verticalConstraintRight = rightButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
let widthConstraintRight = rightButton.widthAnchor.constraint(equalToConstant: 100)
let heightConstraintRight = rightButton.heightAnchor.constraint(equalToConstant: 100)
NSLayoutConstraint.activate([horizontalConstraintRight, verticalConstraintRight, widthConstraintRight, heightConstraintRight])
}
[![Left and Right Aligned View Constraints][1]][1]
[1]: https://i.stack.imgur.com/tMJ8N.jpg
As the cell is getting reuse, you have to put buttons in your xib file. You should not make a button every time and add it in a cell. Try this by adding a button in xib.
class ThemeCell: UITableViewCell {
//MARK:- Initialize View
private let button : UIButton = {
let button = UIButton()
button.setTitle("Hello", .normal)
button.backgroundColor = .red
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
//MARK:- View Life Cycle
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
selectionStyle = .none
}
//MARK:- User Defined Function
private func setup() {
addSubView(button)
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20).isActive = true
button.topAnchor.constraint(equalTo: topAnchor, constant: 20).isActive = true
button.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20).isActive = true
}
}
You can use AutoLayout Constraints like this to setup the button. No need to call it in the cellForRow method.

Prevent UIStackView from compressing UITableView

I am adding a UITableView into vertical UIStackView. That vertical UIStackView is within a UIScrollView.
However the table is not displaying unless I force an explicit Height constraint on it which I obviously don't want to do.
According to this SO question and answer UITableView not shown inside UIStackView this is because "stackview tries to compress content as much as possible"
If I add a UILabel to the StackView it is displayed fine. There is something specific about the UITableView that means it is not. I am using Xamarin and creating the UITableView in code
this.recentlyOpenedPatientsTable = new UITableView()
{
RowHeight = UITableView.AutomaticDimension,
EstimatedRowHeight = 44.0f,
AllowsMultipleSelectionDuringEditing = false,
TranslatesAutoresizingMaskIntoConstraints = false,
Editing = false,
BackgroundColor = UIColor.Clear,
TableFooterView = new UIView(),
ScrollEnabled = false,
};
The UIScrollView is pinned to the Top, Bottom, Left and Right of the View and works fine. It takes the Height I expect.
I have tried both the suggestions in this SO question and neither have worked. I find it odd that I cannot find others having this issue.
Any other suggestions?
Here is a very basic example, using a UITableView subclass to make it auto-size its height based on its content.
The red buttons (in a horizontal stack view) are the first arranged subView in the vertical stack view.
The table is next (green background for the cells' contentView, yellow background for a multi-line label).
And the last arranged subView is a cyan background UILabel:
Note that the vertical stack view is constrained 40-pts from Top, Leading and Trailing, and at least 40-pts from the Bottom. If you add enough rows to the table to exceed the available height, you'll have to scroll to see the additional rows.
//
// TableInStackViewController.swift
//
// Created by Don Mag on 6/24/19.
//
import UIKit
final class ContentSizedTableView: UITableView {
override var contentSize:CGSize {
didSet {
invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
layoutIfNeeded()
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
}
class TableInStackCell: UITableViewCell {
let theLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .yellow
v.textAlignment = .left
v.numberOfLines = 0
return v
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.backgroundColor = .green
contentView.addSubview(theLabel)
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: 0.0),
theLabel.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: 0.0),
theLabel.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: 0.0),
theLabel.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: 0.0),
])
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class TableInStackViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 8
return v
}()
let addButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Add a Row", for: .normal)
v.backgroundColor = .red
return v
}()
let deleteButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.setTitle("Delete a Row", for: .normal)
v.backgroundColor = .red
return v
}()
let buttonsStack: UIStackView = {
let v = UIStackView()
v.axis = .horizontal
v.alignment = .fill
v.distribution = .fillEqually
v.spacing = 20
return v
}()
let theTable: ContentSizedTableView = {
let v = ContentSizedTableView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
let bottomLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
v.textAlignment = .center
v.numberOfLines = 0
v.text = "This label is the last element in the stack view."
// prevent label from being compressed when the table gets too tall
v.setContentCompressionResistancePriority(.required, for: .vertical)
return v
}()
var theTableData: [String] = [
"Content Sized Table View",
"This row shows that the cell heights will auto-size, based on the cell content (multi-line label in this case).",
"Here is the 3rd default row",
]
var minRows = 1
let reuseID = "TableInStackCell"
override func viewDidLoad() {
super.viewDidLoad()
minRows = theTableData.count
view.addSubview(theStackView)
NSLayoutConstraint.activate([
// constrain stack view 40-pts from top, leading and trailing
theStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40.0),
theStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 40.0),
theStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40.0),
// constrain stack view *at least* 40-pts from bottom
theStackView.bottomAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -40.0),
])
buttonsStack.addArrangedSubview(addButton)
buttonsStack.addArrangedSubview(deleteButton)
theStackView.addArrangedSubview(buttonsStack)
theStackView.addArrangedSubview(theTable)
theStackView.addArrangedSubview(bottomLabel)
theTable.delegate = self
theTable.dataSource = self
theTable.register(TableInStackCell.self, forCellReuseIdentifier: reuseID)
addButton.addTarget(self, action: #selector(addRow), for: .touchUpInside)
deleteButton.addTarget(self, action: #selector(deleteRow), for: .touchUpInside)
}
#objc func addRow() -> Void {
// add a row to our data source
let n = theTableData.count - minRows
theTableData.append("Added Row: \(n + 1)")
theTable.reloadData()
}
#objc func deleteRow() -> Void {
// delete a row from our data source (keeping the original rows intact)
let n = theTableData.count
if n > minRows {
theTableData.remove(at: n - 1)
theTable.reloadData()
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return theTableData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! TableInStackCell
cell.theLabel.text = theTableData[indexPath.row]
return cell
}
}

Swift bool data not passed to tableview cell

I have a tableview that gets the data from an API. There is a isUnlocked variable that is also getting value from the API.
But besides the title and picture, the isUnlocked variable still has the default value set in the custom tableview class.
I pass the value in cellForRow function like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if tableView.tag == 1 {
let c = tableView.dequeueReusableCell(withIdentifier: CourseViewIdentifiers.courses.rawValue, for: indexPath) as! CourseViewTableViewCell
c.isUnlocked = tableData[indexPath.row].unlocked
c.selectionStyle = .none
c.courseTitle.text = tableData[indexPath.row].title
c.courseImage.loadImageFrom(urlString: tableData[indexPath.row].pic)
cell = c
}
return cell
}
And this is the custom class for tableview cell:
public var isUnlocked: Bool = false
private lazy var backView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.lightGray.withAlphaComponent(0.2)
view.layer.cornerRadius = 5
view.layer.borderColor = UIColor.lightGray.cgColor
view.layer.borderWidth = 0.5
return view
}()
public lazy var courseImage: WebImageloader = {
let imv = WebImageloader()
imv.contentMode = .scaleAspectFill
return imv
}()
public lazy var courseTitle: UILabel = {
let label = UILabel()
label.font = UIFont.iransans(size: 18)
label.textAlignment = .right
return label
}()
private lazy var courseButton1: UIButton = {
let button = UIButton(type: .system)
if !isUnlocked {
button.setBackgroundImage(#imageLiteral(resourceName: "locked"), for: .normal)
button.isEnabled = false
}else{
button.setBackgroundImage(#imageLiteral(resourceName: "play"), for: .normal)
}
return button
}()
private lazy var courseButton2: UIButton = {
let button = UIButton(type: .system)
button.setBackgroundImage(#imageLiteral(resourceName: "download"), for: .normal)
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .default, reuseIdentifier: CourseViewIdentifiers.courses.rawValue)
print(isUnlocked,"***************")
setupUI()
contentView.backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
private func setupUI() {
backView.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(backView)
backView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 15).isActive = true
backView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
backView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5).isActive = true
backView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5).isActive = true
backView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
courseImage.translatesAutoresizingMaskIntoConstraints = false
backView.addSubview(courseImage)
courseImage.rightAnchor.constraint(equalTo: backView.rightAnchor, constant: -10).isActive = true
courseImage.centerYAnchor.constraint(equalTo: backView.centerYAnchor).isActive = true
courseImage.heightAnchor.constraint(equalToConstant: 50).isActive = true
courseImage.widthAnchor.constraint(equalToConstant: 50).isActive = true
courseImage.layer.cornerRadius = 25
courseImage.clipsToBounds = true
courseButton1.translatesAutoresizingMaskIntoConstraints = false
backView.addSubview(courseButton1)
courseButton1.leftAnchor.constraint(equalTo: backView.leftAnchor, constant: 10).isActive = true
courseButton1.centerYAnchor.constraint(equalTo: backView.centerYAnchor).isActive = true
courseButton1.heightAnchor.constraint(equalToConstant: 30).isActive = true
courseButton1.widthAnchor.constraint(equalToConstant: 30).isActive = true
courseButton1.layer.cornerRadius = 15
courseButton1.clipsToBounds = true
if isUnlocked {
courseButton2.translatesAutoresizingMaskIntoConstraints = false
backView.addSubview(courseButton2)
courseButton2.leftAnchor.constraint(equalTo: courseButton1.rightAnchor, constant: 10).isActive = true
courseButton2.centerYAnchor.constraint(equalTo: backView.centerYAnchor).isActive = true
courseButton2.heightAnchor.constraint(equalToConstant: 30).isActive = true
courseButton2.widthAnchor.constraint(equalToConstant: 30).isActive = true
courseButton2.layer.cornerRadius = 15
courseButton2.clipsToBounds = true
}
courseTitle.translatesAutoresizingMaskIntoConstraints = false
backView.addSubview(courseTitle)
courseTitle.rightAnchor.constraint(equalTo: courseImage.leftAnchor, constant: -10).isActive = true
courseTitle.topAnchor.constraint(equalTo: courseImage.topAnchor).isActive = true
courseTitle.leftAnchor.constraint(equalTo: courseButton1.rightAnchor, constant: 10)
}
The isUnlocked variable has the default False value no matter what I set it in the cellForRow function. Guys it's killing me please help.
I think your value is being set, but your UI is not being updated because it only gets set during initialization, where obviously the value of the Bool will always be the default value (false). So instead of calling setUpUI() in init(), call it after dequeue-ing.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = UITableViewCell()
if tableView.tag == 1 {
let c = tableView.dequeueReusableCell(withIdentifier: CourseViewIdentifiers.courses.rawValue, for: indexPath) as! CourseViewTableViewCell
c.isUnlocked = tableData[indexPath.row].unlocked
c.selectionStyle = .none
c.courseTitle.text = tableData[indexPath.row].title
c.courseImage.loadImageFrom(urlString: tableData[indexPath.row].pic)
c.setUpUI()
//print(c.isUnlocked)
cell = c
}
return cell
}
Or atleast move the set up of courseButton2 to another method and call that method.
Problem solved by adding this block of code:
DispatchQueue.main.async {
if DownloadManager.isDownloading(url: self.course_url!) {
self.courseButton2.alpha = 0.5
self.courseButton2.isEnabled = false
}
}
Just add this code to the cell, and your ok to go!

Resources