I want in UITableViewCell add customized UIView (will be analog segmented control)
I wrote subclass `protocol ITISegmentedViewDelegate: class {
func segmentedViewButtonChanged(index: Int)
}
public protocol ITISegmentedViewDataSource : NSObjectProtocol {
#available(iOS 2.0, *)
func segmentedView(itemsInSegmentedView: ITISegmentedView) -> [String]
}
public class ITISegmentedView: UIView {
var delegate: ITISegmentedViewDelegate?
var dataSource: ITISegmentedViewDataSource?
var selectedItem = -1
override init(frame: CGRect) {
super.init(frame: frame)
}
required public init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.addButtons()
}
private func addButtons(){
if delegate == nil || dataSource == nil{
return
}
let height = frame.height
let width = frame.width
let array = dataSource!.segmentedView(self)
let totalItem = array.count
var startX = CGFloat(0)
for var index = 0; index < totalItem; ++index{
let button = UIButton(frame: CGRectMake(startX, 0, width/CGFloat(totalItem), height))
button.setTitle(array[index], forState: UIControlState.Normal)
button.tag = index
button.addTarget(button, action: "onButtonPressed", forControlEvents: .TouchUpInside)
startX += width/CGFloat(totalItem)
addSubview(button)
}
if totalItem>0{
selectedItem = 0
delegate?.segmentedViewButtonChanged(0)
}
}
func onButtonPressed(button: UIButton){
if selectedItem != button.tag{
delegate?.segmentedViewButtonChanged(button.tag)
selectedItem = button.tag
}
}
}`
In storyboard added UIView and set class ITISegmentedView
in my ViewController:
let cell = tableView.dequeueReusableCellWithIdentifier( cellName, forIndexPath: indexPath)
let seg = (cell.viewWithTag(1) as! ITISegmentedView)
seg.dataSource = self
seg.delegate = self
PROBLEM:
init(coder aDecoder: NSCoder) calls on dequeueReusableCell and at this moment data source and delegate is not set, so ITISegmentedView doesn't work.
Fall back to an empty dataSource when you encounter nil.
Also, try not to use !, but rather ? and ?? to always take into account that an Optional is... well. Optional.
Related
So I'm creating an application that will need a checklist so I was able to create a custom checkbox class to make the checkbox and add that onto my uiButtons which work fine but upon opening up and building the app again it does not save state of the checkbox so how can I make it that the button input will be added to user defaults using this code so when I open the app again the checkbox input will have saved and be returned?
import UIKit
class CheckBox: UIButton {
//images
let checkedImage = UIImage(named: "checkedd_checkbox")
let unCheckedImage = UIImage(named: "uncheckedd_checkbox")
//bool propety
#IBInspectable var isChecked:Bool = false{
didSet{
self.updateImage()
}
}
override func awakeFromNib() {
self.addTarget(self, action: #selector(CheckBox.buttonClicked), for: UIControl.Event.touchUpInside)
self.imageView?.contentMode = .scaleAspectFit
self.imageEdgeInsets = UIEdgeInsets(top: 5.0, left: 0.0, bottom: 5.0, right: 50.0)
self.updateImage()
}
func updateImage() {
if isChecked == true{
self.setImage(checkedImage, for: [])
}else{
self.setImage(unCheckedImage, for: [])
}
}
#objc func buttonClicked(sender:UIButton) {
if(sender == self){
isChecked = !isChecked
}
}
}
Just set Userdefault in buttonClicked action like
#objc func buttonClicked(sender:UIButton) {
if(sender == self){
isChecked = !isChecked
UserDefaults.standard.set(isChecked, forKey: "give a spesific name here")
}
}
And check button is selected status in
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let getCheckStatus = UserDefaults.standard.bool(forKey: "given spesific name here")
self.isChecked = getCheckStatus
updateImage()
}
If you load this view from storyboard or xib use required init?(coder aDecoder: NSCoder) , If you add manually use override init(frame: CGRect) {... }
I have a following setup:
UploadedCell
DisplayUploadsVC
UploadHelper with delegate that tracks
progress (this is singleton)
In my controller I have a timer in cellForItemAt that gets the progress for uploadId that is currently uploading and update the cell upload item.
In my cell I use prepareForReuse and set my upload to nil.
But again when I scroll and cells reuse I see duplicate cells. I also see when I pullToRefress or go to the end of results to get more results from server.
Just not sure what I'm missing or if I can use this kind of implementation with timer in collection view cell to get the progress.
UploadedCell
class UploadedCell: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// MARK: Variables declaration
let uploadBadge = UIImageView(image: #imageLiteral(resourceName: "uploads") , contentMode: .scaleAspectFit)
let uploadName = UILabel(font: UIFont.openSansSemiboldFontOfSize(18))
let statusName = UILabel(font: UIFont.openSansSemiboldFontOfSize(18))
#objc lazy var moreButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "dots-menu")?.withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = .lightGray
button.addTarget(self, action: #selector(handleMore), for: .touchDown)
return button
}()
// MARK - didSet
var upload: UploadModel.UploadItem? {
didSet {
uploadName.text = upload?.title
statusName.text = upload?.status
}
}
// MARK: - Main Init
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .white
setupViews()
}
override func prepareForReuse() {
self.upload = nil
super.prepareForReuse()
}
}
DisplayUploadsVC
class DisplayUploadsVC: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// MARK: - Properties
/// various object init
var uploadCell: UploadCell = UploadCell()
var uploadProgress = (progress: Float(0), uploadId: "")
var progressTimer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
UploadHelper.shared.uploadHelperDelegate = self
setupViews()
setupEmptyBackgroundView()
fetchUploads()
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! UploadCell
cell.upload = nil
var upload = isSearching ? filteredResults[indexPath.item] : results?[indexPath.item]
cell.upload = upload
// get uploads with paging if we are paginating
if (self.uploadProgress.uploadId == upload?.id && self.uploadProgress.progress < 99) {
progressTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
upload?.status = String(format: NSLocalizedString("Uploading %.0f%%", comment: ""), self.uploadProgress.progress)
cell.upload = upload
if self.uploadProgress.progress > 99.9 {
self.progressTimer.invalidate()
upload?.status = NSLocalizedString("Upload DONE", comment: "")
cell.upload = upload
}
}
}
return cell
}
extension DisplayUploadsVC : UploadHelperProgress {
func showProgress(progress: Float, uploadId: String) {
self.uploadProgress.progress = progress
self.uploadProgress.uploadId = uploadId
}
}
UploadHelper
protocol UploadHelperProgress : class {
func showProgress(progress: Float, uploadId: String)
}
private var id: String?
private let progressBlock = { bytesWritten, bytesTotal in
var progress = Float(bytesWritten) / Float(bytesTotal)
UploadHelper.shared.uploadHelperDelegate?.showProgress(progress: progress * 100, uploadId: id ?? "")
} as UploadProgressBlock
class UploadHelper: NSObject {
/// delegate
weak var uploadHelperDelegate: UploadHelperProgress?
/// singleton
static let shared = UploadHelper()
func upload(fileUrl: URL, fileUploadUrl: String, uploadId: String) {
id = uploadId
//
// upload logic
//
upload?.progressBlock = progressBlock
upload?.resume()
}
}
I'm getting the following error when I try to use an instance of MySegmentControl in the code below. The error occurs right after the app is being launched.
Any idea what am I missing?
Fatal error: Use of unimplemented initializer 'init(frame:)' for class 'TestingSubclassing.MySegmentControl'
Subclass of UISegementedControl
import UIKit
class MySegmentControl: UISegmentedControl {
init(actionName: Selector) {
let discountItems = ["One" , "Two"]
super.init(items: discountItems)
self.selectedSegmentIndex = 0
self.layer.cornerRadius = 5.0
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
self.addTarget(self, action: actionName, for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
ViewController
import UIKit
class ViewController: UIViewController {
let segmentOne: MySegmentControl = {
let segment1 = MySegmentControl(actionName: #selector(segmentAction))
return segment1
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(segmentOne)
}
#objc func segmentAction (sender: UISegmentedControl) {
print("segmentAction")
}
}
You could call super.init(frame and insert the segments manually.
And you have to add a target parameter to the custom init(actionName method.
class MySegmentControl: UISegmentedControl {
init(actionName: Selector, target: Any?) {
super.init(frame: .zero)
insertSegment(withTitle: "Two", at: 0, animated: false)
insertSegment(withTitle: "One", at: 0, animated: false)
self.selectedSegmentIndex = 0
self.layer.cornerRadius = 5.0
self.backgroundColor = UIColor.red
self.layer.borderWidth = 1
self.layer.borderColor = UIColor.blue.cgColor
self.addTarget(target, action: actionName, for: .valueChanged)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I need to create a very similar class of the RatingControl class that you make when you create the FoodTracker tutorial. Only difference with mine is that I need that class to be able to create different instances of the rating control, that vary with the types of images. So instead of just having my positivePointRatingButtons UIButton array, I need it to be able to instantiate either the negativePointRatingButtons or superPointRatingButtons as-well. I just dont want to make another whole class just for this purpose, and I am new so I figure Id get some help.
import UIKit
#IBDesignable class PointRatingControl: UIStackView {
//MARK: Properties
private var positivePointRatingButtons = [UIButton]()
private var negativePointRatingButtons = [UIButton]()
private var superPointRatingButtons = [UIButton]()
var rating = 0 {
didSet {
updatePointButtonSelectionStates()
}
}
#IBInspectable var circleType: Int = 1 {
didSet {
setupButtons()
}
}
#IBInspectable var circleSize: CGSize = CGSize(width: 30.0, height: 30.0) {
didSet {
setupButtons()
}
}
#IBInspectable var circleCount: Int = 10 {
didSet {
setupButtons()
}
}
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupButtons()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setupButtons()
}
//MARK: Button Action
#objc func ratingButtonTapped(button: UIButton) {
guard let index = positivePointRatingButtons.index(of: button) else {
fatalError("The button, \(button), is not in the positiveRatingButtons array: \(positivePointRatingButtons)")
}
// Calculate the rating of the selected button
let selectedRating = index + 1
if selectedRating == rating {
// If the selected star represents the current rating, reset the rating to 0.
rating = 0
} else {
// Otherwise set the rating to the selected star
rating = selectedRating
}
}
//MARK: Private Methods
private func setupButtons() {
// Clear any existing buttons
for button in positivePointRatingButtons {
removeArrangedSubview(button)
button.removeFromSuperview()
}
positivePointRatingButtons.removeAll()
// Load button images, since its #IDDesignable in order to show in the interface builder you have to specifyexplicitly the catalog's bundle, as opposed to just using UIImage(named:) method
let bundle = Bundle(for: type(of: self))
let emptyCircle = UIImage(named: "greenCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
let selectedCircle = UIImage(named: "greenFilledCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
let highlightedCircle = UIImage(named: "greenSelectedCirclePhoto", in: bundle, compatibleWith: self.traitCollection)
for _ in 0..<circleCount {
let button = UIButton()
button.setImage(emptyCircle, for: .normal)
button.setImage(selectedCircle, for: .selected)
button.setImage(highlightedCircle, for: .highlighted)
button.setImage(highlightedCircle, for: [.highlighted, .selected])
button.translatesAutoresizingMaskIntoConstraints = false
button.heightAnchor.constraint(equalToConstant: circleSize.height).isActive = true
button.widthAnchor.constraint(equalToConstant: circleSize.width).isActive = true
button.addTarget(self, action: #selector(PointRatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
addArrangedSubview(button)
positivePointRatingButtons.append(button)
}
updatePointButtonSelectionStates()
}
private func updatePointButtonSelectionStates() {
for (index, button) in positivePointRatingButtons.enumerated() {
// If the index of a button is less than the rating, that button should be selected.
button.isSelected = index < rating
}
}
}
Id like to be able to use #IBInspectable aswell using the circleType property I defined so that I can use like 1, 2, 3 Integers as representations for each case.
I figured out how to do it. I just made a switch case to load up different images based on the ratingType variable
I am using a custom UICollectionViewFlowLayout class to achieve multi-scroll behaviour of scroll view. No problem in that.
But to make custom selection and deselection, I need to use custom code inside shouldSelectItemAt function for proper selection/deselection.
Here is the code for it:
Inside MyCustomCollectionViewController:
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
if(collectionView == customContentCollectionView){
let cell:MyContentCell = collectionView.cellForItem(at: indexPath)! as! MyCollectionViewController.MyContentCell
if(self.bubbleArray[indexPath.section][indexPath.item].umid != ""){
// DESELECTION LOGIC
if(previouslySelectedIndex != nil){
let (bubbleBorder, bubbleFill) = getBubbleColor(selected: false)
// following line is error prone (executes but may or may not fetch the cell, sometimes deselect sometimes doesn't)
let prevCell = try collectionView.cellForItem(at: previouslySelectedIndex) as? MyCollectionViewController.MyContentCell
prevCell?.shapeLayer.strokeColor = bubbleBorder.cgColor
prevCell?.shapeLayer.fillColor = bubbleFill.cgColor
prevCell?.shapeLayer.shadowOpacity = 0.0
prevCell?.labelCount.textColor = bubbleBorder
}
previouslySelectedIndex = []
previouslySelectedIndex = indexPath
// SELECTION LOGIC
if(self.bubbleArray[indexPath.section][indexPath.item].interactions != ""){
let (bubbleBorder, bubbleFill) = getBubbleColor(selected: true)
cell.shapeLayer.strokeColor = bubbleBorder.cgColor
cell.shapeLayer.fillColor = bubbleFill.cgColor
cell.shapeLayer.shadowOffset = CGSize(width: 0, height: 2)
cell.shapeLayer.shadowOpacity = 8
cell.shapeLayer.shadowRadius = 2.0
cell.labelCount.textColor = UIColor.white
}
}
return false
}
return true
}
The code for MyContentCell:
class MyContentCell: UICollectionViewCell{ // CODE TO CREATE CUSTOM CELLS
var gradient = CAGradientLayer()
var shapeLayer = CAShapeLayer()
var horizontalLine = UIView()
var verticalLeftLine = UIView()
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
let labelCount: UILabel = {
let myLabel = UILabel()
return myLabel
}()
func setup(){ // initializing cell components here as well as later in cellForItemAt method
............
}
override func prepareForReuse() {
self.shapeLayer.fillColor = UIColor.white.cgColor
self.shapeLayer.shadowOpacity = 0.0
}
}
May be the problem is, when a cell is selected and just dragged out of screen, its perhaps might not being destroyed, that's why the prepare for reuse function is not tracking it to make the deselection. So kindly tell me how to deselect a cell which is just gone outside the screen (visible index)?
You need to invalidate layout
collectionView?.collectionViewLayout.invalidateLayout()