I have a calendarView made up of collectionView. It is a custom calendarView derived using mathematical calculations.
The seventh row marks Saturday and it's holiday so the font color is red for the all the labels of seventh column.
However, when I swipe or navigate to other days, the red color labels are scattered in random order which is untraceable. A screenshot is herewith:
How did this occur?
In my dequeueReusableCell method I have cell configured for holiday as:
cell.isHoliday = (indexPath.row + 1) % 7 == 0 ? true : false
And this is the logic for holiday in my custom collectionViewCell.
#IBOutlet var dateLabel: UILabel!
#IBOutlet var englishDateLabel: UILabel!
#IBOutlet var tithiLabel: UILabel!
var isToday: Bool = false {
didSet {
self.contentView.backgroundColor = isToday ? Colors.Palette.LightGreen : UIColor.white
}
}
var isHoliday: Bool = false {
didSet {
if isHoliday {
tithiLabel.textColor = Colors.Palette.DarkRed
dateLabel.textColor = Colors.Palette.DarkRed
englishDateLabel.textColor = Colors.Palette.DarkRed
}
else {
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
}
}
The number of red labels on top of each collectionview cells goes on increasing as I swipe to next month. Why is this happening and how can I stop this from happening?
You are missing else part:
var isHoliday: Bool = false {
didSet {
if isHoliday {
tithiLabel.textColor = Colors.Palette.DarkRed
dateLabel.textColor = Colors.Palette.DarkRed
englishDateLabel.textColor = Colors.Palette.DarkRed
}
else {
tithiLabel.textColor = UIColor.black
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
}
}
This may be because the cell is being reused and you are not implemented any logic in prepareForReuse method of your custom cell class. In this method try setting text colour properties to nil.
The right way to deal with old data showing up in reused cells is to override prepeareForReuse in your custom cell
open override func prepareForReuse() {
super.prepareForReuse()
tithiLabel.textColor = UIColor.black
dateLabel.textColor = UIColor.black
englishDateLabel.textColor = UIColor.black
}
Clear out old values (by assigning them to nil) or set defaults to all values that might not necessarily be set after the cell is reused. This way, even if the new value(s) are not explicitly set to the cell, you are sure that old values are not being retained.
Related
I'm writing a demo to show user's tweets.
The question is:
Every time I scroll to the bottom and then scroll back, the tweet's images and comments are reloaded, even the style became mess up. I know it something do with dequeue, I set Images(which is an array of UIImageView) to [] every time after dequeue, but it is not working. I'm confused and couldn't quite sleep....
Here is core code of my TableCell(property and Images set), which provide layout:
class WechatMomentListCell: UITableViewCell{
static let identifier = "WechatMomentListCell"
var content = UILabel()
var senderAvatar = UIImageView()
var senderNick = UILabel()
var images = [UIImageView()]
var comments = [UILabel()]
override func layoutSubviews() {
//there is part of Image set and comments
if images.count != 0 {
switch images.count{
case 1:
contentView.addSubview(images[0])
images[0].snp.makeConstraints{ (make) in
make.leading.equalTo(senderNick.snp.leading)
make.top.equalTo(content.snp.bottom)
make.width.equalTo(180)
make.height.equalTo(180)
}
default:
for index in 0...images.count-1 {
contentView.addSubview(images[index])
images[index].snp.makeConstraints{ (make) in
make.leading.equalTo(senderNick.snp.leading).inset(((index-1)%3)*109)
make.top.equalTo(content.snp.bottom).offset(((index-1)/3)*109)
make.width.equalTo(90)
make.height.equalTo(90)
}
}
}
}
if comments.count != 0, comments.count != 1 {
for index in 1...comments.count-1 {
comments[index].backgroundColor = UIColor.gray
contentView.addSubview(comments[index])
comments[index].snp.makeConstraints{(make) in
make.leading.equalTo(senderNick)
make.bottom.equalToSuperview().inset(index*20)
make.width.equalTo(318)
make.height.equalTo(20)
}
}
}
}
Here is my ViewController, which provide datasource:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let tweetCell = tableView.dequeueReusableCell(withIdentifier: WechatMomentListCell.identifier, for: indexPath) as? WechatMomentListCell else {
fatalError("there is no WechatMomentList")
}
let tweet = viewModel.tweetList?[indexPath.row]
for i in tweet?.images ?? [] {
let flagImage = UIImageView()
flagImage.sd_setImage(with: URL(string: i.url))
tweetCell.images.append(flagImage)
}
for i in tweet?.comments ?? [] {
let flagComment = UILabel()
flagComment.text = "\(i.sender.nick) : \(i.content)"
tweetCell.comments.append(flagComment)
}
return tweetCell
}
The Images GET request has been define at ViewModel using Alamofire.
The firsttime is correct. However, If I scroll the screen, the comments will load again and images were mess up like this.
I found the problem in your tableview cell. in cell you have two variables like this.
var images = [UIImageView()]
var comments = [UILabel()]
Every time you using this cell images and comments are getting appended. make sure you reset these arrays every time you use this cell. like setting theme empty at initialization.
screenshot
I have a problem with piecharts not showing in my custom tableview cell.
The tableview cells are added through storyboard, each cell contains a small icon image, two labels and a UIView which I set in the view inspector as PieChartView.
The small icon image and the text in the two labels is showing fine, no problem there. For the piecharts however, I get no error message but simply none of the charts is displayed. The table shows up, is filled with the proper texts in the labels but the piechartview is empty except the middle hole of the piechart. When I click one of these, the piechartview is displayed - not totally correct (only one of the two pie slices is displayed, the other part is missing).
The touch event therefore makes the slice visible, but I want the piechart be visible for all cells upon running the cell for row function.
I have added the code for the tableviewcontroller and the tableviewcell. Would be great, if someone could point out my error. I have researched and tried a lot, among others the following stack overflow resources:
Charts not plotting in tableViewCell
iosChart not displaying LineChartView points in UITableViewCell
Adding a SubView to UITableViewCell doesn't get displayed
How to implement iOS Chart in a tableview cell?
The screenshot shows the situation after I click a few of the invisible piecharts. They become visible, also when I then scroll down the table some more (not all) of piecharts in random cells are visible, some are not.
Code for tableviewcontroller:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "easyAndDiffAllWordsCell", for: indexPath) as! BVBResultsDiffAndEasyAllWordsGraphTableViewCell
let easyVoc = parsedInEasyVocStructures[indexPath.row]
//get the current voc for the writings
let currentVoc = BVBVocabularyManager.getVoc()
cell.label1.text = currentVoc.kanji
cell.label2.text = currentVoc.kanji2
let image = UIImage(named: "plus")
cell.plusMinusImage.image = image
//set the percentages
cell.percentageSolved = arrayOfSuccessPercentagesForPieChart
cell.percentageNotSolved = arrayOfNegativeSuccessPercentagesForPieChart
cell.setChart(forIndexNo:indexPath.row, dataPoints: months, valuesSolved: arrayOfSuccessPercentagesForPieChart, valuedNonSolved: arrayOfNegativeSuccessPercentagesForPieChart)
cell.setNeedsDisplay()
cell.pieChartView.clipsToBounds = true
cell.pieChartView.layer.masksToBounds = true
cell.pieChartView.contentMode = .scaleAspectFit
return cell
}
And for the tableViewCell:
class BVBResultsDiffAndEasyAllWordsGraphTableViewCell: UITableViewCell {
#IBOutlet weak var kanjiL: UILabel!
#IBOutlet weak var translationL: UILabel!
#IBOutlet weak var pieChartView: PieChartView!
#IBOutlet weak var plusMinusImage: UIImageView!
var testconditions: Array<String>?
var percentageSolved: Array<Int>?
var percentageNotSolved: Array<Int>?
var solvedPercentageDataEntry = PieChartDataEntry(value: 0)
var nonSolvedPercentageDataEntry = PieChartDataEntry(value: 0)
var percentageSolvedNonSolvedDataEntries = [PieChartDataEntry]()
override func awakeFromNib() {
super.awakeFromNib()
solvedPercentageDataEntry.label = NSLocalizedString("solved", comment: "piechart label for the solved area")
pieChartView.chartDescription?.text = ""
pieChartView.legend.enabled = false
pieChartView.setExtraOffsets(left: 2, top: 0, right: 2, bottom: 0)
pieChartView.holeRadiusPercent = 2.8
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
//not used
}
func setChart(forIndexNo: Int, dataPoints: [String], valuesSolved: [Int], valuedNonSolved: [Int]){
var dataEntries: [PieChartDataEntry] = []
solvedPercentageDataEntry = PieChartDataEntry(value: Double(valuesSolved[forIndexNo]), label: "")
nonSolvedPercentageDataEntry = PieChartDataEntry(value:Double(valuedNonSolved[forIndexNo]), label: "")
dataEntries = [solvedPercentageDataEntry, nonSolvedPercentageDataEntry]
percentageSolvedNonSolvedDataEntries = [solvedPercentageDataEntry, nonSolvedPercentageDataEntry]
let pieChartDataSet = PieChartDataSet(entries: percentageSolvedNonSolvedDataEntries, label: nil)
pieChartDataSet.drawValuesEnabled = false
let pieChartData = PieChartData(dataSet: pieChartDataSet)
let colors = [UIColor.themeColor(), UIColor.red]
pieChartDataSet.colors = colors as! [NSUIColor]
pieChartView.data = pieChartData
pieChartView.notifyDataSetChanged()
}
}
I test your code. It works fine.
And the middle hole, maybe it's the configuration problem. Try to add this
pieChartDataSet.drawIconsEnabled = false
pieChartDataSet.sliceSpace = 1
pieChartDataSet.highlightColor = UIColor.white
pieChartDataSet.entryLabelColor = UIColor.white
pieChartDataSet.selectionShift = 0
pieChartView.holeRadiusPercent = 0.5
pieChartView.transparentCircleRadiusPercent = 0.0
Hope this helps
I am creating UIStackView and in a cycle creating some amount of UILabel and UISwitch pairs that are added to UIStackView. How now I can get the status of UISwitch is it ON or OFF?
func addAnswrsToTheStack(using answers: [Answer]){
createHorizontalStackView()
horizontalStackView.addArrangedSubview(questionProgressView)
var questionSwitch: UISwitch!
for answer in answers {
createHorizontalStackView()
let label: UILabel = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .left
label.text = answer.text
horizontalStackView.addArrangedSubview(label)
questionSwitch = UISwitch()
questionSwitch.isOn = false
horizontalStackView.addArrangedSubview(questionSwitch)
}
}
You can try
for (index,answer) in answers.enumerated() {
....
questionSwitch = UISwitch()
questionSwitch.tag = index
questionSwitch.isOn = false
questionSwitch.addTarget(self, action: #selector(switchChanged), for:.valueChanged)
}
#objc func switchChanged(_ mySwitch: UISwitch) {
let value = mySwitch.isOn
print(mySwitch.tag , value)
}
You get the value of a UISwitch the same way you set it, by calling isOn. You can loop through all the views in your stackView, check if the view is a switch with
for view in horizontalStackView.arrangedSubviews {
if view is UISwitch {
// check the value here
}
}
You don't know the state state of a switch for each answer since you only create the switch, but you don't save that association. A clean way would be a wrappertype that has a Answer and a UISwitch. But let's do it without for now.
You need return a map that tells you what switch is related what question. The type [Answer:UISwitch] will work and thats it. You now can look up each Switch and its state for an Answer.
func addAnswrsToTheStack(using answers: [Answer]) -> [Answer:UISwitch] {
createHorizontalStackView()
horizontalStackView.addArrangedSubview(questionProgressView)
var questionSwitch: UISwitch!
var switchMap = [Answer:UISwitch]()
for answer in answers {
createHorizontalStackView()
let label: UILabel = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.textAlignment = .left
label.text = answer.text
horizontalStackView.addArrangedSubview(label)
questionSwitch = UISwitch()
questionSwitch.isOn = false
switchMap[Answer] = questionSwitch
horizontalStackView.addArrangedSubview(questionSwitch)
}
return switchMap
}
My question is: When the UITextField is empty, how do I click the "Backspace" button to go to the previous UITextField? I have been struggling trying to do this in my code below?
Second Question: How do I only allow 1 character to get entered in the UITextField?
I am new at Swift code and trying to learn. Any help would be great.
What I am trying to do is have the user be able to type in a code in the 6 UITextFields and be able to click the "Backspace" button on any one of the UITextFields with only allowing the user to enter one number in each UITextField.
Code Below:
#objc func textFieldDidChange(textfield: UITextField) {
let text = textfield.text!
if text.utf16.count == 0 {
switch textfield {
case textField2:
textField1.becomeFirstResponder()
textField1.backgroundColor = UIColor.blue
textField1.tintColor = .clear
case textField3:
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.blue
textField2.tintColor = .clear
case textField4:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.blue
textField3.tintColor = .clear
case textField5:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.blue
textField4.tintColor = .clear
case textField6:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.blue
textField5.tintColor = .clear
textField6.resignFirstResponder()
textField6.backgroundColor = UIColor.blue
textField6.tintColor = .clear
default:
break
}
}
else if text.utf16.count == 1 {
switch textfield {
case textField1:
textField1.backgroundColor = UIColor.black
textField1.textColor = .white
textField1.tintColor = .clear
textField2.becomeFirstResponder()
textField2.backgroundColor = UIColor.black
textField2.textColor = .white
textField2.tintColor = .clear
case textField2:
textField3.becomeFirstResponder()
textField3.backgroundColor = UIColor.black
textField3.textColor = .white
textField3.tintColor = .clear
case textField3:
textField4.becomeFirstResponder()
textField4.backgroundColor = UIColor.black
textField4.textColor = .white
textField4.tintColor = .clear
case textField4:
textField5.becomeFirstResponder()
textField5.backgroundColor = UIColor.black
textField5.textColor = .white
textField5.tintColor = .clear
case textField5:
textField6.becomeFirstResponder()
textField6.backgroundColor = UIColor.black
textField6.textColor = .white
textField6.tintColor = .clear
case textField6:
textField6.resignFirstResponder()
default:
break
}
}
}
I'd just like to point out that I'm still relatively new to iOS and Swift in general, but even with just a few minutes of searching, I was able to find some seeds of ideas which provided me with the suggested solution.
Based on your (improved) question, I believe a different approach is required. What you really don't want to use a text component. "Why"?
I here you ask. Because they don't actually provide you with the functionality that you want and come with a considerable overhead.
For this, what you really want is more control. You want to know when a key is pressed and you want to respond to it (I know, sounds like a text component, but) and be notified when more extended functionality occurs, like the delete key is pressed.
After a few minutes of research, some trial and error, I found that the UIKeyInput is more along the lines of what you want.
It will tell you when text is inserted and, more importantly, will tell you when Delete is pressed
The added benefit is, you can filter the input directly. You can take the first character from the String and ignore the rest or auto fill the following elements with the remaining text. You can perform validation (for numerical only content) and what ever else you might want to do
So, I started a really new project, added a UILabel to the UIViewController in the storyboard, bound it to the source and implemented the UIKeyInput protocol as such...
class ViewController: UIViewController {
override var canBecomeFirstResponder: Bool {
return true
}
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
becomeFirstResponder()
}
}
extension ViewController: UIKeyInput {
var hasText: Bool {
return true
}
func insertText(_ text: String) {
print(text)
label.text = text
}
func deleteBackward() {
print("Delete backward")
}
}
I ran the project and when a key was typed, the label was updated with the new key and when delete was pressed, the Delete backward text was printed to console.
Now. You have some choices to make. To use a single UIViewController and (maybe) a series of UILabels and manage interactions within it, so when a key is typed, you present the next label as the input focus (and when delete is pressed, you move back) or do you create a series of UIControls which represent each digit and manage via some delegate call back process.
You may also need to implement the UITextInputTraits protocol, which will allow you to control the keyboard presented
You might also like to have a read through Responding to Keyboard Events on iOS, CustomTextInputView.swift and Showing the iOS keyboard without a text input which were just some of the resources I used to hobble this basic example together with.
you can use this extension for your second question:
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
when you add this extension to your project you can see an extra attribute in "Attribute Inspector" tab and you can set the max length of UITextField.
My problem is, that I have a tableview with a number of cells. Inside of those cells there are images, that get a green border if they are being taped. If I have a lot of cells and I tap for example the first pic in the first cell, that's when the problem happens. When I start scrolling down the table a little faster, other cells of the same type also have the first picture with green border, even though they are in cell number 13 or something. I think that's because they are reusable cells, but how can I make sure, that only the one cell that is being taped, keeps the change? Hope you guys got what I meant, I know it's kinda confusing. Here is one of the two custom cells, with the code that turns the border green. The images of type bouncingRoundImages, are just UIImages, I created the custom subclass of UIImage to make them bounce.
class TwoPicsTableViewCell: UITableViewCell {
#IBOutlet var containerView: UIView!
#IBOutlet var firstImage: bouncingRoundImageView!
#IBOutlet var secondImage: bouncingRoundImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
containerView.layer.cornerRadius = 10
containerView.clipsToBounds = true
setupFirstImage()
setupSecondImage()
}
func setupFirstImage() {
let tappedOne = UITapGestureRecognizer(target: self, action: #selector(checkPicTwo))
firstImage.addGestureRecognizer(tappedOne)
}
func setupSecondImage() {
let tappedTwo = UITapGestureRecognizer(target: self, action: #selector(checkPicOne))
secondImage.addGestureRecognizer(tappedTwo)
}
func checkPicTwo() {
firstImage.bouncing()
if secondImage.layer.borderWidth != 0 {
secondImage.layer.borderWidth = 0
}
}
func checkPicOne() {
secondImage.bouncing()
if firstImage.layer.borderWidth != 0 {
firstImage.layer.borderWidth = 0
}
}
}
It sounds like a reusable cell problem. Try setting the image to nil and borderWidth to 0 in prepareForReuse.
override func prepareForReuse()
{
super.prepareForReuse()
firstImage.image = nil
secondImage.image = nil
firstImage.layer.borderWidth = 0
secondImage.layer.borderWidth = 0
}