Search code examples
iosswiftuisegmentedcontrol

Custom Segmented Control Not Switching Views


I made a custom UIControl:

import UIKit

    @IBDesignable class FeedViewSC: UIControl {

    fileprivate var labels = [UILabel]()
    var thumbView = UIView()

    var items: [String] = ["Tab1", "Tab2"] {
        didSet {
            setupLabels()
        }

    }

    var selectedIndex : Int = 0 {
        didSet{
            displayNewSelectedIndex()
        }
    }

    @IBInspectable var font : UIFont! = UIFont.systemFont(ofSize: 13) {
        didSet {
            setFont()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)

        setupView()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupView()
    }

    func setupView() {

        layer.cornerRadius = 5
        layer.borderColor = UIColor(red: 239/255, green: 239/255, blue: 239/255, alpha: 1).cgColor
        layer.borderWidth = 2

        backgroundColor = UIColor(red: 239/255, green: 239/255, blue: 239/255, alpha: 1)

        setupLabels()

        insertSubview(thumbView, at: 0)

    }


    func setupLabels() {
        for label in labels {
            label.removeFromSuperview()
        }

        labels.removeAll(keepingCapacity: true)

        for index in 1...items.count {
            let label = UILabel(frame: CGRect.zero)
            label.text = items[index-1]
            label.textAlignment = .center
            label.font = UIFont(name: "Nunito",size: 17)
            label.textColor = UIColor(red: 51/255, green: 51/255, blue: 51/255, alpha: 1)
            self.addSubview(label)
            labels.append(label)
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        var selectFrame = self.bounds
        let newWidth = selectFrame.width / CGFloat(items.count)
        selectFrame.size.width = newWidth
        thumbView.frame = selectFrame
        thumbView.backgroundColor = UIColor(red: 255/255, green: 255/255, blue: 255/255, alpha: 1)
        thumbView.layer.cornerRadius = 5

        let labelHeight = self.bounds.height
        let labelWidth = self.bounds.width / CGFloat(labels.count)

        for index in 0...labels.count - 1 {

            let label = labels[index]

            let xPosition = CGFloat(index) * labelWidth
            label.frame = CGRect(x: xPosition, y: 0, width: labelWidth, height: labelHeight)
        }

    }

    override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
        let location = touch.location(in: self)

        var calculatedIndex: Int?
        for (index, item) in labels.enumerated() {
            if item.frame.contains(location){
                calculatedIndex = index
            }
        }
        if calculatedIndex != nil {
            selectedIndex = calculatedIndex!
            sendActions(for: .valueChanged)
        }

        return false

    }


    func displayNewSelectedIndex (){

        if(self.selectedIndex == -1){
            self.selectedIndex = self.items.count-1
        }

        let label = labels[selectedIndex]


        UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.90, initialSpringVelocity: 0.8, options: [], animations: {

            self.thumbView.frame = label.frame

        }, completion: nil)
    }

    func setFont(){
        for item in labels {
            item.font = font
        }
    }

}

I emeded it into my ViewController and tried to have it programitcally switch between two views:

import UIKit

    class FeedViewController: UIViewController {

    let feedViewSC = FeedViewSC()

    let firstView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.red
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let secondView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.blue
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let (width, height) = UIScreen.main.bounds.wh

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.white
        view.addSubview(firstView)
        view.addSubview(secondView)

        setupContainerViews()

        firstView.isHidden = false
        secondView.isHidden = true

        myTravel.selectedIndex = 0

        let feedControl = FeedViewSC(frame: CGRect(x: 65, y: 5, width: width-130, height: 35))
        feedControl.autoresizingMask = [.flexibleWidth,.flexibleHeight]

        self.navigationController?.navigationBar.addSubview(feedControl)
        feedControl.addTarget(self, action: #selector(segmentedControlSwitch), for: .valueChanged)
    }

    func segmentedControlSwitch(_ sender: FeedViewSC) {
        switch feedViewSC.selectedIndex {

        case 0:
            firstView.isHidden = false
            secondView.isHidden = true

        case 1:
            firstView.isHidden = true
            secondView.isHidden = false


        default:
            break;
        }
    }

    func setupContainerViews() {

        firstView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        firstView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8).isActive = true
        firstView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
        firstView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

        secondView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        secondView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8).isActive = true
        secondView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
        secondView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

    }

    override func viewDidAppear(_ animated: Bool) {
        let img = UIImage()
        self.navigationController?.navigationBar.shadowImage = img
        self.navigationController?.navigationBar.setBackgroundImage(img, for: UIBarMetrics.default)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

extension CGRect {
    var wh: (w: CGFloat, h: CGFloat) {
        return (size.width, size.height)
    }
}

I'm not sure what I'm doing that's wrong. For some reason, the code does not switch between firstView and secondView. Currently, it is just showing the first view. please keep in mind i am doing this without storyboards and all programmatically.


Solution

  • The issue seems in segmentedControlSwitch method. You should be using sender in switch case instead of local variable which might not be initialized.

    func segmentedControlSwitch(_ sender: myTripsSC) {
        switch sender.selectedIndex {
    
        case 0:
            firstView.isHidden = false
            secondView.isHidden = true
    
        case 1:
            firstView.isHidden = true
            secondView.isHidden = false
    
    
        default:
            break;
        }
    }
    

    Edit: your vc should look like this:

    import UIKit
    
    class FeedViewController: UIViewController {
    
    let feedViewSC = FeedViewSC()
    
    let firstView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.red
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let secondView: UIView = {
        let view = UIView()
        view.backgroundColor = UIColor.blue
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()
    
    let (width, height) = UIScreen.main.bounds.wh
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.white
        view.addSubview(firstView)
        view.addSubview(secondView)
    
        setupContainerViews()
    
        firstView.isHidden = false
        secondView.isHidden = true
    
        //myTravel.selectedIndex = 0
    
        let feedControl = FeedViewSC(frame: CGRect(x: 65, y: 5, width: width-130, height: 35))
        feedControl.autoresizingMask = [.flexibleWidth,.flexibleHeight]
    
        self.navigationController?.navigationBar.addSubview(feedControl)
        feedControl.addTarget(self, action: #selector(segmentedControlSwitch), for: .valueChanged)
    }
    
    func segmentedControlSwitch(_ sender: FeedViewSC) {
        switch sender.selectedIndex {
    
        case 0:
            firstView.isHidden = false
            secondView.isHidden = true
    
        case 1:
            firstView.isHidden = true
            secondView.isHidden = false
    
    
        default:
            break;
        }
    }
    
    func setupContainerViews() {
    
        if #available(iOS 9.0, *) {
            firstView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            firstView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            firstView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            firstView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
    
        if #available(iOS 9.0, *) {
            secondView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            secondView.topAnchor.constraint(equalTo: topLayoutGuide.bottomAnchor, constant: 8).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            secondView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
        if #available(iOS 9.0, *) {
            secondView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
        } else {
            // Fallback on earlier versions
        }
    
    }
    
    override func viewDidAppear(_ animated: Bool) {
        let img = UIImage()
        self.navigationController?.navigationBar.shadowImage = img
        self.navigationController?.navigationBar.setBackgroundImage(img, for: UIBarMetrics.default)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    }
    
    extension CGRect {
        var wh: (w: CGFloat, h: CGFloat) {
            return (size.width, size.height)
        }
    }