15 Replies
      Latest reply on Sep 9, 2019 3:28 AM by Claude31
      Romeo84 Level 1 Level 1 (0 points)

        Hi.

         

        According to article "Start Developing iOS Apps (Swift) / Implement a Custom Control"

         

        https://developer.apple.com/library/archive/referencelibrary/GettingStarted/DevelopiOSAppsSwift/ImplementingACustomControl.html#//apple_ref/doc/uid/TP40015214-CH19-SW1

         

        i implemented all the steps in latest XCode environment but stubbed to one issue: when i update any button's property in my created RatingControl it is not affected imediatly on user interface. Only after i click on any button i can see new button's property updated. I even try to force layout update procedure

        button.superview?.superview?.layoutIfNeeded()

        button.superview?.superview?.setNeedsDisplay()

        but without succeed.

         

         

        Could someone advice me what do i need for imediatly apply changes on UI.

         

        Thanks!

        • Re: UIButton under UIStackView do not updated properly
          Claude31 Level 8 Level 8 (6,515 points)

          Food tracker is known as being pretty buggy, so that may be just one more…

           

          The article you reference is really long.

          Could you tell where (copy the part of code) you try to updatea button's property ?

           

          Maybe you could try update the stackView alone :

          button.superview?.setNeedsDisplay()
            • Re: UIButton under UIStackView do not updated properly
              Romeo84 Level 1 Level 1 (0 points)

              Hi Claude31

              I tryed to update stackView and even stackView.superview but all the same.

               

              Here are cutted part of RatingControl UI

               

               

               

              @IBDesignable

              class RatingControl: UIStackView {

               

                  // MARK: Properties

                  private var ratingButtons = [UIButton]()

                  var rating = 0 {didSet { updateButtonSelectionStates() }}

               

                  @IBInspectable

                  var starCount : Int = 5

               

               

               

               

                  //MARK: Button action

                  @objc func ratingButtonTapped(button: UIButton) {

                      print("buttonPressed")

                 

                      guard let indexOfButton = ratingButtons.firstIndex(of: button) else {

                          fatalError("The button \(button) is not in the buttons array \(ratingButtons)")

                      }

                 

                      let selectedIndex = indexOfButton + 1

                 

                      if(selectedIndex == rating) {

                          rating = 0

                      } else {

                          rating = selectedIndex

                      }

                  }

               

                  func updateButtonSelectionStates() {

                      for (index, button) in ratingButtons.enumerated() {

                          print("executing: index: \(index), button: \(button.description)")

                          button.isSelected = index < rating

                          button.superview?.superview?.layoutIfNeeded() // this don't work

                          button.superview?.superview?.setNeedsDisplay() // this don't work

                      }

                  }

              }

               

              Could you suggest maybe kind of contemporary book for people coming from C# like me )

                • Re: UIButton under UIStackView do not updated properly
                  Claude31 Level 8 Level 8 (6,515 points)

                  Where and how did you define that button appearance should change when selected ?

                   

                  Just try this to see if any effect

                   

                      func updateButtonSelectionStates() {
                          for (index, button) in ratingButtons.enumerated() {
                              print("executing: index: \(index), button: \(button.description)")
                              button.isSelected = index < rating
                            if button.isSelected  {
                              button.backgroundColor = UIColor.red
                            } else {
                              button.backgroundColor = UIColor.blue
                            }
                              button.superview?.superview?.layoutIfNeeded() // this don't work
                              button.superview?.superview?.setNeedsDisplay() // this don't work
                          }
                      }
                    • Re: UIButton under UIStackView do not updated properly
                      Romeo84 Level 1 Level 1 (0 points)

                      I tryed, but behavior didn't changed ((

                       

                      When i first time touch the button i don't see any changes. But when i once more touch anything from UI - and magic - RateControl updates. I presume that there is have to be additional calling something like "refreshing" method, or redraw control.

                       

                      PS I tried to programmably add new button and update it appearance from TimerFireUp Procedure - and all work nice. Maybe problem with the bunch stackView->buttons array but i don't think so.

                       

                      Anyway, thanks.

                        • Re: UIButton under UIStackView do not updated properly
                          Claude31 Level 8 Level 8 (6,515 points)

                          On first touch, what do you get from the print ?

                                      print("executing: index: \(index), button: \(button.description)")

                           

                          Can you show the code where you initialize ratingButtons ?

                           

                          In fact, could you copy the complete code of the class ?

                            • Re: UIButton under UIStackView do not updated properly
                              Claude31 Level 8 Level 8 (6,515 points)

                              So, is it solved ?

                              • Re: UIButton under UIStackView do not updated properly
                                Romeo84 Level 1 Level 1 (0 points)

                                Hi Claude31! Sorry for delay - was on vacation in suburb.

                                 

                                First thing:

                                On first touch, what do you get from the print ?

                                            print("executing: index: \(index), button: \(button.description)")

                                 

                                On first touch updateButtonSelectionStates procedure executes properly and i see this:

                                buttonPressed

                                executing: index: 0, button: <UIButton: 0x7fcf0cc22ec0; frame = (0 0; 20 20); opaque = NO; layer = <CALayer: 0x600000ed5a40>>

                                executing: index: 1, button: <UIButton: 0x7fcf0cc231e0; frame = (30 0; 20 20); opaque = NO; layer = <CALayer: 0x600000ed6400>>

                                executing: index: 2, button: <UIButton: 0x7fcf0cc23500; frame = (60 0; 20 20); opaque = NO; layer = <CALayer: 0x600000ed7fe0>>

                                executing: index: 3, button: <UIButton: 0x7fcf0cc23820; frame = (90 0; 20 20); opaque = NO; layer = <CALayer: 0x600000ed5160>>

                                executing: index: 4, button: <UIButton: 0x7fcf0cc23b40; frame = (120 0; 20 20); opaque = NO; layer = <CALayer: 0x600000ed5fa0>>

                                 

                                And nothing happens on user interface... But then i second time click on any button - ui updates regarding previouse button state (not current state).

                                 

                                Here my full code of RatingControl.swift

                                 

                                //
                                //  RatingControl.swift
                                //  HelloWorld.Foods
                                //
                                //  Created by Romeo on 21/08/2019.
                                //  Copyright © 2019 Romeo. All rights reserved.
                                //
                                
                                
                                import UIKit
                                
                                
                                @IBDesignable
                                class RatingControl: UIStackView {
                                    /*
                                    // Only override draw() if you perform custom drawing.
                                    // An empty implementation adversely affects performance during animation.
                                    override func draw(_ rect: CGRect) {
                                        // Drawing code
                                    }
                                    */
                                  
                                    // MARK: Properties
                                    private var ratingButtons = [UIButton]()
                                    var rating = 0 {didSet { updateButtonSelectionStates() }}
                                  
                                    @IBInspectable
                                    var starSize : CGSize = CGSize(width: 44.0, height: 44.0) {
                                        didSet {
                                            setupButtons()
                                        }
                                    }
                                  
                                    @IBInspectable
                                    var starCount : Int = 5 {
                                        didSet {
                                            setupButtons()
                                        }
                                    }
                                  
                                  
                                    // MARK: Initialization
                                  
                                    override init(frame: CGRect) {
                                        super.init(frame: frame)
                                        setupButtons()
                                    }
                                  
                                    required init(coder: NSCoder) {
                                        super.init(coder: coder)
                                        setupButtons()
                                    }
                                  
                                    // MARK: Private buttons
                                  
                                    private func setupButtons()->Void {
                                        let bundle = Bundle(for: type(of: self))
                                        let emptyStar = UIImage(named: "emptyStar", in: bundle, compatibleWith: self.traitCollection)
                                        let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
                                        let highLightedStar = UIImage(named: "highLightedStar", in: bundle, compatibleWith: self.traitCollection)
                                      
                                      
                                        for button in ratingButtons {
                                            removeArrangedSubview(button)
                                            button.removeFromSuperview()
                                        }
                                        ratingButtons.removeAll()
                                      
                                      
                                        for _ in 0..<starCount {
                                            let button = UIButton()
                                            button.backgroundColor = UIColor.blue
                                            //button.isUserInteractionEnabled = true
                                            //button.setImage(emptyStar, for: .normal)
                                            //button.setImage(filledStar, for: .selected)
                                            //button.setImage(highLightedStar, for: .highlighted)
                                            //button.setImage(highLightedStar, for: [.selected, .highlighted])
                                
                                
                                            button.translatesAutoresizingMaskIntoConstraints = false
                                            button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                                            button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                                            button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: UIControl.Event.touchUpInside)
                                          
                                            addArrangedSubview(button)
                                            ratingButtons.append(button)
                                        }
                                    }
                                  
                                    //MARK: Button action
                                    @objc func ratingButtonTapped(button: UIButton) {
                                        print("buttonPressed")
                                      
                                        guard let indexOfButton = ratingButtons.firstIndex(of: button) else {
                                            fatalError("The button \(button) is not in the buttons array \(ratingButtons)")
                                        }
                                      
                                        let selectedIndex = indexOfButton + 1
                                      
                                        if(selectedIndex == rating) {
                                            rating = 0
                                        } else {
                                            rating = selectedIndex
                                        }
                                    }
                                  
                                    func updateButtonSelectionStates() {
                                        for (index, button) in ratingButtons.enumerated() {
                                            print("executing: index: \(index), button: \(button.description)")
                                            button.isSelected = index < rating
                                          
                                            if button.isSelected  {
                                                button.backgroundColor = UIColor.red
                                            } else {
                                                button.backgroundColor = UIColor.blue
                                            }
                                          
                                            //button.backgroundColor = UIColor(red: 0.1, green: 0.80, blue: 0.1, alpha: 1.0)
                                            button.setTitle("s", for: .selected)
                                            //button.layoutIfNeeded()
                                          
                                            button.superview?.superview?.layoutIfNeeded()
                                            button.superview?.superview?.setNeedsDisplay()
                                            button.superview?.setNeedsDisplay()
                                        }
                                    }
                                  
                                    @objc
                                    func testBtnUpdate()->Void {
                                        print("testBtnUpdate()")
                                        let t0 = ratingButtons.first?.isSelected
                                        if let t1 = t0 {
                                            ratingButtons.first?.isSelected = !t1
                                        }
                                    }
                                
                                
                                }
                                
                                  • Re: UIButton under UIStackView do not updated properly
                                    Claude31 Level 8 Level 8 (6,515 points)

                                    And nothing happens on user interface... But then i second time click on any button - ui updates regarding previouse button state (not current state).

                                     

                                    What should occur ? Color change ? Other ?

                                     

                                    Could you check the value of rating in updateButtonSelectionStates.

                                     

                                    UIStackView doc says:

                                    The UIStackView is a nonrendering subclass of UIView; that is, it does not provide any user interface of its own. Instead, it just manages the position and size of its arranged views. As a result, some properties (like backgroundColor) have no effect on the stack view.

                                     

                                    So, I you may have to ask for redraw buttons themselves:

                                     

                                        func updateButtonSelectionStates() {
                                            for (index, button) in ratingButtons.enumerated() {
                                                print("executing: index: \(index), button: \(button.description)")
                                                button.isSelected = index < rating
                                                print("rating", rating, "button.isSelected", button.isSelected)          // Just to check
                                                if button.isSelected  {
                                                    button.backgroundColor = UIColor.red
                                                } else {
                                                    button.backgroundColor = UIColor.blue
                                                }
                                           
                                                //button.backgroundColor = UIColor(red: 0.1, green: 0.80, blue: 0.1, alpha: 1.0)
                                                button.setTitle("s", for: .selected)
                                                //button.layoutIfNeeded()
                                           
                                                button.superview?.superview?.layoutIfNeeded()
                                                button.superview?.superview?.setNeedsDisplay()
                                                button.superview?.setNeedsDisplay()
                                                button.setNeedsDisplay()          // ADD this
                                           }
                                        }

                                     

                                    In your code :

                                            if (selectedIndex == rating) {
                                                rating = 0
                                            } else {
                                                rating = selectedIndex
                                            }

                                     

                                    Do you reset rating to zero if you type the present rating ?

                                      • Re: UIButton under UIStackView do not updated properly
                                        Romeo84 Level 1 Level 1 (0 points)

                                        The thing is that  rating in updateButtonSelectionStates changes properly and it reset to zero if i type to present rating. Only issue that i can't see present changes in user interface.

                                         

                                        See my action in screenshots

                                         

                                        At first i click 3-rd star - i see nothing

                                        https://i.postimg.cc/ydvh2Bsg/scr00.png

                                        Then i click last star - i see updated control with previouse state - first 3 stars active

                                        https://i.postimg.cc/X7ZK1Thq/scr01.png

                                         

                                        Now i click 2nd star - i see updated control with all stars active

                                        https://i.postimg.cc/PJ7Wr4Ld/scr02.png

                                         

                                        and so on.....

                                        https://i.postimg.cc/L4QBTHxG/scr03.png

                                        • Re: UIButton under UIStackView do not updated properly
                                          Romeo84 Level 1 Level 1 (0 points)

                                          And to clarify situation, i've updated RatingControl code as this:

                                           

                                          //
                                          //  RatingControl.swift
                                          //  HelloWorld.Foods
                                          //
                                          //  Created by Romeo on 21/08/2019.
                                          //  Copyright © 2019 Romeo. All rights reserved.
                                          //
                                          
                                          
                                          import UIKit
                                          
                                          
                                          @IBDesignable
                                          class RatingControl: UIStackView {
                                              /*
                                              // Only override draw() if you perform custom drawing.
                                              // An empty implementation adversely affects performance during animation.
                                              override func draw(_ rect: CGRect) {
                                                  // Drawing code
                                              }
                                              */
                                              
                                              // MARK: Properties
                                              private var ratingButtons = [UIButton]()
                                              var rating = 0 {didSet { updateButtonSelectionStates() }}
                                              
                                              @IBInspectable
                                              var starSize : CGSize = CGSize(width: 44.0, height: 44.0) {
                                                  didSet {
                                                      setupButtons()
                                                  }
                                              }
                                              
                                              @IBInspectable
                                              var starCount : Int = 5 {
                                                  didSet {
                                                      setupButtons()
                                                  }
                                              }
                                              
                                              
                                              // MARK: Initialization
                                              
                                              override init(frame: CGRect) {
                                                  super.init(frame: frame)
                                                  setupButtons()
                                              }
                                              
                                              required init(coder: NSCoder) {
                                                  super.init(coder: coder)
                                                  setupButtons()
                                              }
                                              
                                              // MARK: Private buttons
                                              
                                              private func setupButtons()->Void {
                                                  let bundle = Bundle(for: type(of: self))
                                                  let emptyStar = UIImage(named: "emptyStar", in: bundle, compatibleWith: self.traitCollection)
                                                  let filledStar = UIImage(named: "filledStar", in: bundle, compatibleWith: self.traitCollection)
                                                  let highLightedStar = UIImage(named: "highLightedStar", in: bundle, compatibleWith: self.traitCollection)
                                                  
                                                  
                                                  for button in ratingButtons {
                                                      removeArrangedSubview(button)
                                                      button.removeFromSuperview()
                                                  }
                                                  ratingButtons.removeAll()
                                                  
                                                  
                                                  for _ in 0..<starCount {
                                                      let button = UIButton()
                                                      //button.backgroundColor = UIColor.blue
                                                      button.isUserInteractionEnabled = true
                                                      button.setImage(emptyStar, for: .normal)
                                                      button.setImage(filledStar, for: .selected)
                                                      button.setImage(highLightedStar, for: .highlighted)
                                                      button.setImage(highLightedStar, for: [.selected, .highlighted])
                                          
                                          
                                                      button.translatesAutoresizingMaskIntoConstraints = false
                                                      button.heightAnchor.constraint(equalToConstant: starSize.height).isActive = true
                                                      button.widthAnchor.constraint(equalToConstant: starSize.width).isActive = true
                                                      button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: UIControl.Event.touchUpInside)
                                                      
                                                      addArrangedSubview(button)
                                                      ratingButtons.append(button)
                                                  }
                                              }
                                              
                                              //MARK: Button action
                                              @objc func ratingButtonTapped(button: UIButton) {
                                                  print("buttonPressed")
                                                  
                                                  guard let indexOfButton = ratingButtons.firstIndex(of: button) else {
                                                      fatalError("The button \(button) is not in the buttons array \(ratingButtons)")
                                                  }
                                                  
                                                  let selectedIndex = indexOfButton + 1
                                                  
                                                  if(selectedIndex == rating) {
                                                      rating = 0
                                                  } else {
                                                      rating = selectedIndex
                                                  }
                                              }
                                              
                                              func updateButtonSelectionStates() {
                                                  for (index, button) in ratingButtons.enumerated() {
                                                      print("executing: index: \(index), button: \(button.description)")
                                                      
                                                      button.isSelected = index < rating
                                          
                                          
                                                      //button.superview?.superview?.layoutIfNeeded()
                                                      //button.superview?.superview?.setNeedsDisplay()
                                                      //button.superview?.setNeedsDisplay()
                                                  }
                                              }
                                              
                                              @objc
                                              func testBtnUpdate()->Void {
                                                  print("testBtnUpdate()")
                                                  let t0 = ratingButtons.first?.isSelected
                                                  if let t1 = t0 {
                                                      ratingButtons.first?.isSelected = !t1
                                                  }
                                              }
                                          
                                          
                                          }
                                          
                                            • Re: UIButton under UIStackView do not updated properly
                                              Claude31 Level 8 Level 8 (6,515 points)

                                              Thanks.

                                               

                                              But you didn't answer my questions:

                                              And nothing happens on user interface... But then i second time click on any button - ui updates regarding previouse button state (not current state).

                                              - What should occur when you tap ? Color change ? Other ?

                                               

                                              - Could you check the value of rating in updateButtonSelectionStates.

                                                • Re: UIButton under UIStackView do not updated properly
                                                  Romeo84 Level 1 Level 1 (0 points)

                                                  Claude31

                                                  thanks.

                                                   

                                                  So then i tap the button, color of the buttons to the left must change.

                                                   

                                                   

                                                  The thing is that  rating in updateButtonSelectionStates changes properly and it reset to zero if i type to present rating. Only issue that i can't see present changes in user interface.

                                                    • Re: UIButton under UIStackView do not updated properly
                                                      Claude31 Level 8 Level 8 (6,515 points)

                                                      I notice in your code:

                                                       

                                                              for _ in 0..<starCount {

                                                                  let button = UIButton()

                                                                  button.isUserInteractionEnabled = true

                                                                  button.setImage(emptyStar, for: .normal)

                                                                  button.setImage(filledStar, for: .selected)

                                                                  button.setImage(highLightedStar, for: .highlighted)

                                                                  button.setImage(highLightedStar, for: [.selected, .highlighted])

                                                       

                                                      So, image is specified twice for .selected and .highlighted

                                                       

                                                       

                                                      Doc states also:

                                                      var isSelected: Bool { get set }

                                                      Discussion

                                                      Set the value of this property to true to select it or false to deselect it. Most controls do not modify their appearance or behavior when selected, but some do.

                                                       

                                                      So, you could try

                                                          func updateButtonSelectionStates() {
                                                              for (index, button) in ratingButtons.enumerated() {
                                                                  print("executing: index: \(index), button: \(button.description)")
                                                                 
                                                                  button.isSelected = index < rating
                                                                  button.setImage(filledStar, for: .selected)     // Call again
                                                                  button.setNeedsDisplay()
                                                      
                                                                  //button.superview?.superview?.layoutIfNeeded()
                                                                  //button.superview?.superview?.setNeedsDisplay()
                                                                  //button.superview?.setNeedsDisplay()
                                                              }
                                                          }