Search code examples
iosswiftoverlappingorientation-changesibdesignable

IBDesignable ButtonView right border overlapping on orientation change (Landscape)


I have a problem managing my UITableView with IBDesignable ButtonView and orientation. Basically, the problem happens when I switch to landscape, right border of customView inside UITableViewCell is overlapping. This is only happening when I change the orientation to landscape. It should be able to show right border properly inside the cell.

Anyone have any ideas on this problem? I included a image on how my layout is organized and the behavior:

In Portrait, working as expected

In Landscape, right border overlapping

To add more details, each ButtonView has boolean defined as User Defined Runtime Attributes in Storyboard, which is true for first 2 buttons.

Here is the IBDesignable ButtonView implementation

import UIKit

@IBDesignable
class ButtonView: UIView {

    // MARK: - Properties
    @IBInspectable var image: UIImage? {
        didSet {
            imageView.image = image
        }
    }

    @IBInspectable var title: String? {
        didSet {
            titleLabel.text = title
        }
    }

    @IBInspectable var hasRightBorder: Bool = false

    @IBInspectable var disabledColor: UIColor = Colors.lightGray
    @IBInspectable var enabledColor: UIColor = Colors.primary
    @IBInspectable var iconSize: CGFloat = 16.0 {
        didSet {
            imageView.addConstraints(width: iconSize, height: iconSize)
        }
    }
    var imageView = UIImageView()
    var titleLabel = UILabel()
    var button = UIButton(type: .custom)

    var isEnabled = true {
        didSet {
            if isEnabled {
                imageView.tintColor = enabledColor
                titleLabel.textColor = enabledColor
            } else {
                imageView.tintColor = disabledColor
                titleLabel.textColor = disabledColor
            }

            button.isEnabled = isEnabled
        }
    }

    var buttonClosure: (() ->Void)?

    // MARK: - Initializers
    override init(frame: CGRect) {
        super.init(frame: frame)

        setupView()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        setupView()
    }

    // MARK: - View lifecycle
    override func layoutSubviews() {
        super.layoutSubviews()

        if hasRightBorder {
            layer.addBorder(edge: .right, color: Colors.lightGray, thickness: 1.0)
        } else {
            layer.addBorder(edge: .right, color: UIColor.clear, thickness: 0.0)
        }
    }

    // MARK: - Actions
    @objc fileprivate func buttonTapped() {
        buttonClosure?()
    }

    // MARK: - Private API
    fileprivate func setupView() {
        imageView.tintColor = enabledColor
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)
        imageView.topAnchor.constraint(equalTo: topAnchor, constant: 5.0).isActive = true
        imageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true

        titleLabel.textColor = Colors.primary
        titleLabel.font = FontBook.regular.of(size: .extraSmall)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(titleLabel)
        titleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -5.0).isActive = true
        titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true

        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
        button.translatesAutoresizingMaskIntoConstraints = false
        addSubview(button)
        button.fillSuperview(with: -10.0)
    }

}

addBorder implementation

extension CALayer {

    func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {
        let border = CALayer();

        switch edge {
        case UIRectEdge.top:
            border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: thickness)
            break
        case UIRectEdge.bottom:
            border.frame = CGRect(x:0, y:self.frame.height - thickness, width:self.frame.width, height:thickness)
            break
        case UIRectEdge.left:
            border.frame = CGRect(x:0, y:0, width: thickness, height: self.frame.height)
            break
        case UIRectEdge.right:
            border.frame = CGRect(x:self.frame.width - thickness, y: 0, width: thickness, height:self.frame.height)
            break
        default:
            break
        }

        border.backgroundColor = color.cgColor;

        addSublayer(border)
    }

}

Here is how it looks in Storyboard


Solution

  • I resolve the issue using different approach. I came to know by looking at this answer that many things including rotating a device calls layoutSubview. that's why a border was repeating. I'm posting my solution incase someone facing the same issue.

    First, instead of defining a boolean hasRightBorder, I created a border of width 1 inside Storyboard

    Then, I define a UIView inside ButtonView class, set the width and frame heightened add it to the view. Worked like charm :)

    @IBDesignable
    class ButtonView: UIView {
    
        // MARK: - Properties
        @IBInspectable var image: UIImage? {
            didSet {
                imageView.image = image
            }
        }
    
        //Add the rightBorder support
        @IBInspectable var border: CGFloat = 1.0 {
            didSet {
                view.addConstraints(width: border, height: frame.size.height)
            }
        }
    
        // MARK: - Initializers
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            setupView()
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
    
            setupView()
        }
    
        //view to add right border
        var view = UIView()
    
        // MARK: - Private API
        fileprivate func setupView() {
    
            //Set the color & add it to view
            view.backgroundColor = Colors.lightGray
            view.translatesAutoresizingMaskIntoConstraints = false
            addSubview(view)
    
            view.rightAnchor.constraint(equalTo: rightAnchor, constant: 0).isActive = true
    
        }
    
    }