TextField returning Nil - ios

I have custom UITextField in the UICollectionView, the textfield returns null always even if I pass some value from it , and the app crashes on the button click
Code:
func accountCollectionView(_ collectionView: UICollectionView?, cellForItemAt indexPath: IndexPath?) -> UICollectionViewCell?
{
let cell : RBLoginCell = collectionView!.dequeueReusableCell(withReuseIdentifier: "RBLoginCell", for: indexPath!) as! RBLoginCell
cell.backgroundColor = UIColor.clear
switch indexPath!.row {
case 0:
let txtFldUserId = cell.txtFld
txtFldUserId?.delegate = self
txtFldUserId?.placeholder = "Email Address"
txtFldUserId?.tag = 1
txtFldUserId?.keyboardType = .emailAddress
txtFldUserId?.returnKeyType = .next
txtFldUserId?.isSecureTextEntry = false
break
case 1:
let txtFldPassword = cell.txtFld
txtFldPassword?.delegate = self
txtFldPassword?.placeholder = "Password"
txtFldPassword?.tag = 2
txtFldPassword?.returnKeyType = .done
txtFldPassword?.isSecureTextEntry = true
break
default:
break
}
cell.setNeedsLayout()
return cell
}
on this method the app crashes while checking for the charcter count
func loginAction(sender:UIButton!)
{
if (txtFldUserId?.text?.characters.count)! > 0 {
if (txtFldPassword?.text?.characters.count)! > 0 {
user?.emailAddress = txtFldUserId?.text
user?.password = txtFldPassword?.text
postLoginRequest(user)
Custom cell class
class RBLoginCell : RBParentCell {
var txtFld : CustomTextField?
var separator : UIView?
override init(frame: CGRect) {
super.init(frame: frame)
txtFld = getTextField()
separator = getImageView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func getLabel() -> UILabel? {
let _lblInfo = UILabel(frame: CGRect.zero)
_lblInfo.backgroundColor = UIColor.clear
_lblInfo.textAlignment = .left
_lblInfo.textColor = UIColor.white
_lblInfo.numberOfLines = 0
addSubview(_lblInfo)
return _lblInfo
}
func getTextField(_ frame: CGRect) -> CustomTextField? {
let textField = CustomTextField(frame: frame)
textField.borderStyle = .none
textField.placeholder = ""
textField.backgroundColor = UIColor.clear
textField.autocorrectionType = .no
// no auto correction support
textField.keyboardType = .default
// use the default type input method (entire keyboard)
textField.returnKeyType = .next
textField.enablesReturnKeyAutomatically = false
textField.clearButtonMode = .never
textField.text = ""
textField.contentVerticalAlignment = .center
textField.autocapitalizationType = .none
//textField.frame = CGRectMake(X, Y,Width,Height);
textField.alpha = 1.0
textField.isUserInteractionEnabled = true
let lblleft: UILabel? = getLabel()
lblleft?.frame = CGRect(x: 0, y: 20, width: self.frame.size.width, height: self.frame.size.height)
lblleft?.backgroundColor = UIColor.clear
lblleft?.text = "\n+91"
lblleft?.font = UIFont(name: Constants.HelveticaNeue, size: 14)//FONT_HelveticaNeue(14.0)
lblleft?.textColor = UIColor(hexString: "555555")
textField.leftView = lblleft
textField.leftViewMode = .never
addSubview(textField)
return textField
}
override func getImageView() -> UIImageView? {
let imageView = UIImageView(frame: CGRect.zero)
imageView.backgroundColor = UIColor.lightGray
addSubview(imageView)
return imageView
}
override func layoutSubviews() {
super.layoutSubviews()
txtFld?.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height-1)
separator?.frame = CGRect(x: 0, y: (txtFld?.frame.size.height)!, width: frame.size.width, height: 1)
}

You have initialised
let txtFldUserId = cell.txtFld
and
let txtFldPassword = cell.txtFld
in your cellForItemAt method, how can you access it in loginAction method then?
If you have initialised txtFldUserId and txtFldPassword in your view controller, then remove let from the cellForItemAt method like
txtFldUserId = cell.txtFld
txtFldPassword = cell.txtFld
[I am assuming you have initialised in your viewController
var txtFldUserId: UITextField?
var txtFldPassword: UITextField?
because you are not getting compiler error in your loginAction method ]

Related

Display data from tableviewcell into Pop-up UIView

I have a tableView showing multiple tasks already created by the user. When the user click on a task (tableView cell) i want to present a pop-up showing more info about the user's task. I already created the popUp view and it's showing fine but the data (category, date, hour) is not showing inside the popUp. I don't know how to access my data and put it into the pop-up View when the user click on the row. Here's what i tried so far :
MyTasksCollectionCell
enum DisplayedTasks {
case current
case past
}
class MyTasksCollectionCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
var displayedTasks = DisplayedTasks.current
var tasks = [Add]()
var pastTasks = [Add]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! MyTasksTableCell
cell.accessoryType = .disclosureIndicator
let task = {() -> Add in
switch (displayedTask) {
case .current:
// First segment tapped
return self.tasks[indexPath.row]
case past:
// Second segment tapped
return self.pastTasks[indexPath.row]
}
}()
cell.categoryLabel.text =
"\(task.category)"
cell.dateLabel.text =
"\(task.date)"
cell.hourLabel.text =
"\(task.hour)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let task = {() -> Add in
switch (displayedTask) {
case .current:
// First segment tapped
return self.tasks[indexPath.row]
case past:
// Second segment tapped
return self.pastTasks[indexPath.row]
}
}()
let selectedCell = tableView.cellForRow(at: indexPath) as? MyTasksDetailView//Warning message here !!! "Cast from 'UITableViewCell?' to unrelated type 'MyTasksDetailView' always fails"
selectedCell?.category.text = "\(task.category)"
selectedCell?.hour.text = "\(task.hour)"
selectedCell?.date.text = "\(task.date)"
print (selectedCell?.category.text)
let popupView = MyTasksDetailView()
UIApplication.shared.keyWindow?.addSubview(popupView)
}
extension UIApplication {
var keyWindow: UIWindow? {
// Get connected scenes
return UIApplication.shared.connectedScenes
// Keep only active scenes, onscreen and visible to the user
.filter { $0.activationState == .foregroundActive }
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
}
MyTasksDetailView
class MyTasksDetailView: UIView {
var setCategory: String? {
didSet {
categoryLabel.text = setCategory ?? ""
}
}
var setDate: String? {
didSet {
dateLabel.text = setDate ?? ""
}
}
var setHour: String? {
didSet {
hourLabel.text = setHour ?? ""
}
}
let categoryLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 28, weight: .bold)
// label.text = "category"
label.textAlignment = .center
label.textColor = .label
return label
}()
let dateLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
// label.text = "date"
label.textAlignment = .center
label.numberOfLines = 3
label.textColor = .label
return label
}()
let hourLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 18, weight: .bold)
// label.text = "hour"
label.textAlignment = .center
label.numberOfLines = 3
label.textColor = .label
return label
}()
let container: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
v.clipsToBounds = true
v.backgroundColor = .white
v.layer.cornerRadius = 24
v.backgroundColor =
// 1
UIColor { traitCollection in
// 2
switch traitCollection.userInterfaceStyle {
case .dark:
// 3
v.layer.borderColor = UIColor.label.cgColor
return UIColor.systemBackground
default:
// 4
v.layer.borderColor = UIColor.black.cgColor
return UIColor.systemBackground
}
}
return v
}()
lazy var stack: UIStackView = {
let stack = UIStackView(arrangedSubviews: [categoryLabel, dateLabel, hourLabel])
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .vertical
return stack
}()
#objc func animateOut() {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
self.alpha = 0
}) { (complete) in
if complete {
self.removeFromSuperview()
}
}
}
#objc func animateIn() {
self.container.transform = CGAffineTransform(translationX: 0, y: -self.frame.height)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseIn, animations: {
self.container.transform = .identity
self.alpha = 1
})
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateOut)))
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
blurEffectView.frame = bounds
blurEffectView.alpha = 1
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(blurEffectView)
self.frame = UIScreen.main.bounds
self.addSubview(container)
container.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
container.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
container.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.7).isActive = true
container.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.45).isActive = true
container.addSubview(stack)
stack.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
stack.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true
stack.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.5).isActive = true
animateIn()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Add (DataStruct)
struct Add {
static var details: Add = Add()
var category: String = ""
var date: String = ""
var hour: String = ""
var id: String?
func getDict() -> [String: Any] {
let dict = ["category": self.category,
"date": self.date,
"hour": self.hour,
] as [String : Any]
return dict
}
}
I can't see where you set needed data to your MyTasksDetailView.
Add property "task" to MyTasksDetailView (cant find type of your task instance so in my example I used Task)
internal var task: Task? {
didSet {
if let task = task {
setCategory = task.category
setDate = task.date
setHour = task.hour
}
}
}
And set it after init
let popupView = MyTasksDetailView()
popupView.task = task
UIApplication.shared.keyWindow?.addSubview(popupView)

Search Bar textfield cursor color not changing in mac Catalyst it's showing black color ios swift

i also try to change with "searchController.searchBar.searchTextField.tintColor = .white"
but it's not working issue facing after xcode 13 update.
Try to create a custom searchController and into the setup to change the tintColor of all the subviews that are different of UIButton .
Here an example :
class CustomSearchController: UISearchController {
var placeHolder:String?
private var catalogSearchBar = CatalogSearchBar()
override public var searchBar: UISearchBar {
get {
catalogSearchBar.placeholder = placeHolder
return catalogSearchBar
}
}
}
class CatalogSearchBar: UISearchBar {
init() {
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
private func setup() {
backgroundColor = Constants.shared.navigationBar.lightModeBgColor
// text field
let textField = searchTextField
textField.subviews.forEach { (view) in
if ((view as? UIButton) != nil) {
view.tintColor = UIColor.white
}
}
textField.frame.size.height = 35
self.searchTextPositionAdjustment = UIOffset(horizontal: 4, vertical: 0)
textField.layer.cornerRadius = 15
textField.placeholder = self.placeholder
textField.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
textField.layer.masksToBounds = true
textField.layer.backgroundColor = UIColor.white.withAlphaComponent(0.25).cgColor
if let view = textField.value(forKey: "backgroundView") as? UIView {
view.removeFromSuperview()
}
textField.font = UIFont(name: "Montserrat-Regular", size: 15)
textField.textColor = UIColor.white
textField.tintColor = UIColor.white
// search icon
let leftView: UIView = {
let image = UIImage(named: "search")
let padding = 8
let size = 20
let outerView = UIView(frame: CGRect(x: 0, y: 0, width: size + padding, height: size) )
let iconView = UIImageView(frame: CGRect(x: padding, y: 0, width: size, height: size))
iconView.tintColor = UIColor.white
iconView.image = image
outerView.addSubview(iconView)
return outerView
}()
textField.leftView = leftView
}
}

How to add constraints to a collection view cell once the cell is selected?

I am trying to create a feature programmatically so that when a user selects a cell in the collection view the app keeps a count of the image selected and adds it as an overlay. I am also wanting to add the video duration to the bottom of the image if the selection is a video. I know my problem is in my constraints. You can see in the image example below that I am trying to add the count to the top left of the collection view cell, but also when the user deselects a cell the count adjusts so for example if the number 2 in the image below was deselected the number 3 would become 2. For the most part I think I have the code working but I cannot get the constraints to work. With the current configuration I am getting an error (see below) but I do not even know where to begin with this problem.
"Unable to activate constraint with anchors because they have
no common ancestor. Does the constraint or its anchors reference
items in different view hierarchies? That's illegal."
CollectionView:
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
cell.commonInit()
}
}
func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
if let cell = collectionView.cellForItem(at: indexPath) as? TestCVCell {
//Not sure what to put here
}
}
Overlay
class CustomAssetCellOverlay: UIView {
let countSize = CGSize(width: 40, height: 40)
lazy var circleView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.layer.cornerRadius = self.countSize.width / 2
view.alpha = 0.4
return view
}()
let countLabel: UILabel = {
let label = UILabel()
let font = UIFont.preferredFont(forTextStyle: .headline)
label.font = UIFont.systemFont(ofSize: font.pointSize, weight: UIFont.Weight.bold)
label.textAlignment = .center
label.textColor = .white
label.adjustsFontSizeToFitWidth = true
return label
}()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
private func commonInit() {
addSubview(circleView)
addSubview(countLabel)
//***** START - UPDATED BASED ON SUGGESTION IN COMMENTS******
countLabel.translatesAutoresizingMaskIntoConstraints = false
//***** END - UPDATED BASED ON SUGGESTION IN COMMENTS******
countLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor).isActive = true
countLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor).isActive = true
}
}
Collection View Cell
var img = UIImageView()
var overlayView = UIView()
var asset: PHAsset? {
didSet {}
}
var isVideo: Bool = false {
didSet {
durationLabel.isHidden = !isVideo
}
}
override var isSelected: Bool {
didSet { overlay.isHidden = !isSelected }
}
var imageView: UIImageView = {
let view = UIImageView()
view.clipsToBounds = true
view.contentMode = .scaleAspectFill
view.backgroundColor = UIColor.gray
return view
}()
var count: Int = 0 {
didSet { overlay.countLabel.text = "\(count)" }
}
var duration: TimeInterval = 0 {
didSet {
let hour = Int(duration / 3600)
let min = Int((duration / 60).truncatingRemainder(dividingBy: 60))
let sec = Int(duration.truncatingRemainder(dividingBy: 60))
var durationString = hour > 0 ? "\(hour)" : ""
durationString.append(min > 0 ? "\(min):" : ":")
durationString.append(String(format: "%02d", sec))
durationLabel.text = durationString
}
}
let overlay: CustomAssetCellOverlay = {
let view = CustomAssetCellOverlay()
view.isHidden = true
return view
}()
let durationLabel: UILabel = {
let label = UILabel()
label.preferredMaxLayoutWidth = 80
label.backgroundColor = .gray
label.textColor = .white
label.textAlignment = .right
label.font = UIFont.boldSystemFont(ofSize: 20)
return label
}()
func commonInit() {
addSubview(imageView)
imageView.addSubview(overlay)
imageView.addSubview(durationLabel)
imageView.translatesAutoresizingMaskIntoConstraints = false
//***** START - UPDATED BASED ON SUGGESTION IN COMMENTS******
overlay.translatesAutoresizingMaskIntoConstraints = false
overlayView.translatesAutoresizingMaskIntoConstraints = false
//***** END - UPDATED BASED ON SUGGESTION IN COMMENTS******
NSLayoutConstraint.activate([
overlay.topAnchor.constraint(equalTo: imageView.topAnchor),
overlay.bottomAnchor.constraint(equalTo: imageView.bottomAnchor),
overlay.leftAnchor.constraint(equalTo: imageView.leftAnchor),
overlay.rightAnchor.constraint(equalTo: imageView.rightAnchor),
overlayView.centerXAnchor.constraint(equalTo: overlay.centerXAnchor),
overlayView.centerYAnchor.constraint(equalTo: overlay.centerYAnchor),
overlayView.widthAnchor.constraint(equalToConstant: 80.0),
overlayView.heightAnchor.constraint(equalToConstant: 80.0),
]
)
}
//Some other stuff

UITableView with custom cells lags while scrolling

My problem is that UITableView lags quite a lot while scrolling.
This is what I am trying to achieve
Starting from the top I have a simple section header with only one checkbox and one UILabel. Under this header, you can see a custom cell with only one UILabel aligned to the center. This custom cell works like another header for the data that would be shown below (Basically a 3D array). Under these "headers" are custom cells that contain one multiline UILabel and under this label is a container for a variable amount of lines containing a checkbox and an UILabel. On the right side of the cell is also a button (blue/white arrow).
So this means the content is shown like this:
Section header (containing day and date)
Custom UITableViewCell = header (containing some header information)
Custom UITableViewCell (containing data to be shown)
Here is my code:
cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let (isHeader, headerNumber, semiResult) = checkIfIsHeader(section: indexPath.section, row: indexPath.row)
let row = indexPath.row
if isHeader {
let chod = objednavkaDny[indexPath.section].chody[headerNumber+1]
let cell = tableView.dequeueReusableCell(withIdentifier: cellHeaderReuseIdentifier, for: indexPath) as! ObjednavkyHeaderTableViewCell
cell.titleLabel.text = chod.popisPoradiJidla
cell.selectionStyle = .none
return cell
}else{
let chod = objednavkaDny[indexPath.section].chody[headerNumber]
let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ObjednavkyTableViewCell
cell.updateData(objednavka: chod.objednavky[row-semiResult], canSetAmount: self.typDialogu == 3)
return cell
}
}
checkIfIsHeader:
func checkIfIsHeader(section: Int, row: Int) -> (Bool, Int, Int){
if let cachedResult = checkIfHeaderCache[section]?[row] {
return (cachedResult[0] == 1, cachedResult[1], cachedResult[2])
}
var isHeader = false
var semiResult = 0
var headerNumber = -1
for (index, chod) in objednavkaDny[section].chody.enumerated() {
let sum = chod.objednavky.count
if row == semiResult {
isHeader = true
break
}else if row < semiResult {
semiResult -= objednavkaDny[section].chody[index-1].objednavky.count
break
}else {
headerNumber += 1
semiResult += 1
if index != objednavkaDny[section].chody.count - 1 {
semiResult += sum
}
}
}
checkIfHeaderCache[section] = [Int:[Int]]()
checkIfHeaderCache[section]![row] = [isHeader ? 1 : 0, headerNumber, semiResult]
return (isHeader, headerNumber, semiResult)
}
and the main cell that shows the data:
class ObjednavkyTableViewCell: UITableViewCell {
lazy var numberTextField: ObjednavkyTextField = {
let textField = ObjednavkyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
return textField
}()
let mealLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .black
label.textAlignment = .left
label.font = UIFont(name: ".SFUIText", size: 15)
label.numberOfLines = 0
label.backgroundColor = .white
label.isOpaque = true
return label
}()
lazy var detailsButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setImage(UIImage(named: "arrow-right")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.imageView?.tintColor = UIColor.custom.blue.classicBlue
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .right
button.imageEdgeInsets = UIEdgeInsetsMake(10, 0, 10, 0)
button.addTarget(self, action: #selector(detailsButtonPressed), for: .touchUpInside)
button.backgroundColor = .white
button.isOpaque = true
return button
}()
let pricesContainerView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .white
view.isOpaque = true
return view
}()
var canSetAmount = false {
didSet {
canSetAmount ? showNumberTextField() : hideNumberTextField()
}
}
var shouldShowPrices = false {
didSet {
shouldShowPrices ? showPricesContainerView() : hidePricesContainerView()
}
}
var pricesContainerHeight: CGFloat = 0
private let priceViewHeight: CGFloat = 30
var mealLabelLeadingConstraint: NSLayoutConstraint?
var mealLabelBottomConstraint: NSLayoutConstraint?
var pricesContainerViewHeightConstraint: NSLayoutConstraint?
var pricesContainerViewBottomConstraint: NSLayoutConstraint?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func detailsButtonPressed() {
}
func updateData(objednavka: Objednavka, canSetAmount: Bool) {
self.canSetAmount = canSetAmount
if let popisJidla = objednavka.popisJidla, popisJidla != "", popisJidla != " " {
self.mealLabel.text = popisJidla
}else{
self.mealLabel.text = objednavka.nazevJidelnicku
}
if objednavka.objects.count > 1 {
shouldShowPrices = true
setPricesStackView(with: objednavka.objects)
checkIfSelected(objects: objednavka.objects)
}else{
shouldShowPrices = false
self.numberTextField.text = String(objednavka.objects[0].pocet)
//setSelected(objednavka.objects[0].pocet > 0, animated: false)
objednavka.objects[0].pocet > 0 ? setSelectedStyle() : setDeselectedStyle()
}
}
//---------------
func checkIfSelected(objects: [ObjednavkaObject]) {
var didChangeSelection = false
for object in objects { // Checks wether cell should be selected or not
if object.pocet > 0 {
setSelected(true, animated: false)
setSelectedStyle()
didChangeSelection = true
break
}
}
if !didChangeSelection {
setSelected(false, animated: false)
setDeselectedStyle()
}
}
//--------------
func showNumberTextField() {
numberTextField.isHidden = false
mealLabelLeadingConstraint?.isActive = false
mealLabelLeadingConstraint = mealLabel.leadingAnchor.constraint(equalTo: numberTextField.trailingAnchor, constant: 10)
mealLabelLeadingConstraint?.isActive = true
}
func hideNumberTextField() {
numberTextField.isHidden = true
mealLabelLeadingConstraint?.isActive = false
mealLabelLeadingConstraint = mealLabel.leadingAnchor.constraint(equalTo: readableContentGuide.leadingAnchor, constant: 0)
mealLabelLeadingConstraint?.isActive = true
}
func showPricesContainerView() {
hideNumberTextField()
pricesContainerView.isHidden = false
mealLabelBottomConstraint?.isActive = false
pricesContainerViewBottomConstraint?.isActive = true
}
func hidePricesContainerView() {
pricesContainerView.isHidden = true
pricesContainerViewBottomConstraint?.isActive = false
mealLabelBottomConstraint?.isActive = true
}
//--------------
func setSelectedStyle() {
self.backgroundColor = UIColor.custom.blue.classicBlue
mealLabel.textColor = .white
mealLabel.backgroundColor = UIColor.custom.blue.classicBlue
for subview in pricesContainerView.subviews where subview is ObjednavkyPriceView {
let priceView = (subview as! ObjednavkyPriceView)
priceView.titleLabel.textColor = .white
priceView.checkBox.backgroundColor = UIColor.custom.blue.classicBlue
priceView.titleLabel.backgroundColor = UIColor.custom.blue.classicBlue
priceView.backgroundColor = UIColor.custom.blue.classicBlue
}
pricesContainerView.backgroundColor = UIColor.custom.blue.classicBlue
detailsButton.imageView?.tintColor = .white
detailsButton.backgroundColor = UIColor.custom.blue.classicBlue
}
func setDeselectedStyle() {
self.backgroundColor = .white
mealLabel.textColor = .black
mealLabel.backgroundColor = .white
for subview in pricesContainerView.subviews where subview is ObjednavkyPriceView {
let priceView = (subview as! ObjednavkyPriceView)
priceView.titleLabel.textColor = .black
priceView.checkBox.backgroundColor = .white
priceView.titleLabel.backgroundColor = .white
priceView.backgroundColor = .white
}
pricesContainerView.backgroundColor = .white
detailsButton.imageView?.tintColor = UIColor.custom.blue.classicBlue
detailsButton.backgroundColor = .white
}
//-----------------
func setPricesStackView(with objects: [ObjednavkaObject]) {
let subviews = pricesContainerView.subviews
var subviewsToDelete = subviews.count
for (index, object) in objects.enumerated() {
subviewsToDelete -= 1
if subviews.count - 1 >= index {
let priceView = subviews[index] as! ObjednavkyPriceView
priceView.titleLabel.text = object.popisProduktu // + " " + NSNumber(value: object.cena).getFormattedString(currencySymbol: "Kč") // TODO: currencySymbol
priceView.canSetAmount = canSetAmount
priceView.count = object.pocet
priceView.canOrder = (object.nelzeObj == nil || object.nelzeObj == "")
}else {
let priceView = ObjednavkyPriceView(frame: CGRect(x: 0, y: CGFloat(index) * priceViewHeight + CGFloat(index * 5), width: pricesContainerView.frame.width, height: priceViewHeight))
pricesContainerView.addSubview(priceView)
priceView.titleLabel.text = object.popisProduktu // + " " + NSNumber(value: object.cena).getFormattedString(currencySymbol: "Kč") // TODO: currencySymbol
priceView.numberTextField.delegate = self
priceView.canSetAmount = canSetAmount
priceView.canOrder = (object.nelzeObj == nil || object.nelzeObj == "")
priceView.count = object.pocet
pricesContainerHeight += ((index == 0) ? 30 : 35)
}
}
if subviewsToDelete > 0 { // Deletes unwanted subviews
for _ in 0..<subviewsToDelete {
pricesContainerView.subviews.last?.removeFromSuperview()
pricesContainerHeight -= pricesContainerHeight + 5
}
}
if pricesContainerHeight < 0 {
pricesContainerHeight = 0
}
pricesContainerViewHeightConstraint?.constant = pricesContainerHeight
}
func setupView() {
self.layer.shouldRasterize = true
self.layer.rasterizationScale = UIScreen.main.scale
self.backgroundColor = .white
contentView.addSubview(numberTextField)
contentView.addSubview(mealLabel)
contentView.addSubview(detailsButton)
contentView.addSubview(pricesContainerView)
setupConstraints()
}
func setupConstraints() {
numberTextField.anchor(leading: readableContentGuide.leadingAnchor, size: CGSize(width: 30, height: 30))
numberTextField.centerYAnchor.constraint(equalTo: mealLabel.centerYAnchor).isActive = true
detailsButton.anchor(trailing: readableContentGuide.trailingAnchor, size: CGSize(width: 30, height: 30))
detailsButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
mealLabel.anchor(top: contentView.topAnchor, trailing: detailsButton.leadingAnchor, padding: .init(top: 10, left: 0, bottom: 0, right: -10))
mealLabelBottomConstraint = mealLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
mealLabelBottomConstraint?.priority = UILayoutPriority(rawValue: 999)
pricesContainerView.anchor(top: mealLabel.bottomAnchor, leading: readableContentGuide.leadingAnchor, trailing: detailsButton.leadingAnchor, padding: .init(top: 10, left: 0, bottom: 0, right: -10))
pricesContainerViewBottomConstraint = pricesContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10)
pricesContainerViewBottomConstraint?.priority = UILayoutPriority(rawValue: 999)
pricesContainerViewHeightConstraint = pricesContainerView.heightAnchor.constraint(equalToConstant: 0)
pricesContainerViewHeightConstraint?.priority = UILayoutPriority(rawValue: 999)
pricesContainerViewHeightConstraint?.isActive = true
}
}
To conclude how it is done:
tableView.rowHeight is set to UITableViewAutomaticDymension
inside cellForRowAt I get the data from an array and give it to the
cell
all the cells are set up in code using constraints
all the views have set isOpaque = true
heights of the cells are cached
cells are set to rasterize
I also noticed that it lags at certain scroll levels and that sometimes it works just fine and sometimes it lags a lot.
Despite all of the optimization I have done, the tableView still lags while scrolling.
Here is a screenshot from Instruments
Any tip how to improve the scrolling performance is highly appreciated!
(I might have forgotten to include some code/information so feel free to ask me in the comments.)
I can't tell you where the lag happens exactly but when we are talking about lagging during scrolling, it's related to your cellForRowAt delegate method. What happends is that too many things are going on within this method and it's called for every cells that are displaying & going to display. I see that your are trying to cache the result by checkIfHeaderCache but still, there is a for loop at the very beginning to determine header cell.
Suggestions:
I don't know where you get data (objednavkaDny) from but right after you get the data, do a full loop through and determin cell type one by one, and save the result some where base on your design. During this loading time, you can show some loading message on the screen. Then, within the cellForRow method, you should be just simply using things like
if (isHeader) {
render header cell
} else {
render other cell
}
Bottom line:
cellForRow method is not designed to handle heavy calculations, and it will slow down the scrolling if you do so. This method is for assigning values to the cached table view cell only and that's the only thing it is good at.

Create a Utility function for UIActivityIndicator to show and hide depending on the condition in controller

Hello I have created a swift file in which I wrote some utility functions which I used in multiple controllers. I also wrote function for UIActivityIndicator. But somehow its not working as expected.
Here is my function
static func showIndicatorView(backgroundView: UIView,controller: UIViewController)->UIActivityIndicatorView{
let loadingIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))
let backgroundView = UIView()
backgroundView.layer.cornerRadius = 05
backgroundView.clipsToBounds = true
backgroundView.opaque = false
backgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.color = UIColor.whiteColor()
loadingIndicator.startAnimating()
let loadingLabel = UILabel()
loadingLabel.text = "Loading..."
loadingLabel.textColor = UIColor.whiteColor()
let textSize: CGSize = loadingLabel.text!.sizeWithAttributes([NSFontAttributeName: loadingLabel.font ])
loadingLabel.frame = CGRectMake(50, 0, textSize.width, textSize.height)
loadingLabel.center.y = loadingIndicator.center.y
backgroundView.frame = CGRectMake(0, 0, textSize.width + 70, 50)
backgroundView.center = controller.view.center;
controller.view.addSubview(backgroundView)
backgroundView.addSubview(loadingIndicator)
backgroundView.addSubview(loadingLabel)
return loadingIndicator
}
I am doing this in controllers in order to show and hide the indicator
show
Utility.showIndicatorView(backgroundView, controller: self).startAnimating()
hide
Utility.showIndicatorView(backgroundView, controller: self).startAnimating()
Sometimes UiIndicatorView background doesn't remove from the controller. Please check my code and let me know how can I show and hide the uiindicator in one or two lines
Create a class for your loading view and add functions to show and hide the view like this:
import UIKit
class LoadingView: UIView {
override init (frame : CGRect) {
super.init(frame : frame)
let loadingIndicator = UIActivityIndicatorView(frame: CGRectMake(0, 0, 50, 50))
let backgroundView = UIView()
backgroundView.layer.cornerRadius = 05
backgroundView.clipsToBounds = true
backgroundView.opaque = false
backgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.6)
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.color = UIColor.whiteColor()
loadingIndicator.startAnimating()
let loadingLabel = UILabel()
loadingLabel.text = "Loading..."
loadingLabel.textColor = UIColor.whiteColor()
let textSize: CGSize = loadingLabel.text!.sizeWithAttributes([NSFontAttributeName: loadingLabel.font ])
loadingLabel.frame = CGRectMake(50, 0, textSize.width, textSize.height)
loadingLabel.center.y = loadingIndicator.center.y
backgroundView.frame = CGRectMake(0, 0, textSize.width + 70, 50)
backgroundView.center = self.center;
self.addSubview(backgroundView)
backgroundView.addSubview(loadingIndicator)
backgroundView.addSubview(loadingLabel)
}
convenience init () {
self.init(frame:UIScreen.mainScreen().bounds)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func showLoadingView() {
if let rootViewController = UIApplication.topViewController() {
rootViewController.view.addSubview(self)
self.bringSubviewToFront(rootViewController.view)
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
}
}
func hideLoadingView() {
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
self.removeFromSuperview()
}
}
// Get the visible ViewController
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController where top.view.window != nil {
return topViewController(top)
} else if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
Then init your loading view in your ViewController.
let lv = LoadingView()
lv.showLoadingView()
lv.hideLoadingView()

Resources