45

I am new to iOS development.I want to toggle (hide/visible) a subview from parent view.In android there is a way to hide the visibility to gone.

In android

subView.setVisibility(View.GONE);

In iOS

subView.removeFromSuperview()

when i use above function it remove subViewConstraints and mess up my scroll view constraints.

topsubView.bottomAnchor.constraint(equalTo: bottomSubView.topAnchor, constant: 8).isActive = true

when i use the above code it works fine and hide subView.but when i want to make subView visible,it is not showing the subView.

topsubView.bottomAnchor.constraint(equalTo: bottomSubView.topAnchor, constant: 8).isActive = false
self.view.layoutIfNeeded()

Hope you understand my problem.Thanks in Advance.

Krunal
  • 77,632
  • 48
  • 245
  • 261
John
  • 3,769
  • 4
  • 12
  • 23

5 Answers5

43

As I have worked on both iOS & Android, You need to play with constraint outlet in ios to achieve Android functioning. iOS Does not support automatically like Android native support on visibility GONE & VISIBLE

You need to hook the outlet of particular constraint(it may vertical/horizontal/height) you need to set it to 0 & need to manage your UI.

To Hide:

self.viewYourConstraint.constant = 0
self.yourView.hidden = true
self.view.layoutIfNeeded()

To Show:

self.viewYourConstraint.constant = 100//your constant value
self.yourView.hidden = false
self.view.layoutIfNeeded()

Note: If other constraints will be affected because of the update to the constraint above, the following must also need to be called:

self.yourView.setNeedsUpdateConstraints()
Dharman
  • 30,962
  • 25
  • 85
  • 135
Mukesh Lokare
  • 2,159
  • 26
  • 38
  • 1
    Is the hidden part necessary when you are setting the height constraint to 0? – C. Kontos Dec 12 '19 at 15:48
  • Thats how it work in iOS, gone feature is not available on IOS. Somehow even you remove view from superview then you will have to create that view again then.. – Mukesh Lokare Dec 13 '19 at 11:00
  • Well I have similar code without using `self.yourView.hidden = true` and everything works fine since I am making the view's height constraint equal to 0 when I want it hidden. – C. Kontos Dec 17 '19 at 13:08
  • Yes, thats true, But the best practices in the iOS is we should have to make view hidden as well. – Mukesh Lokare Dec 17 '19 at 14:05
26

Try this extension:

extension UIView {
    
    func visiblity(gone: Bool, dimension: CGFloat = 0.0, attribute: NSLayoutAttribute = .height) -> Void {
        if let constraint = (self.constraints.filter{$0.firstAttribute == attribute}.first) {
            constraint.constant = gone ? 0.0 : dimension
            self.layoutIfNeeded()
            self.isHidden = gone
        }
    }
}

How you can use this....

@IBOutlet weak var testView: UIView?
@IBAction func testVisibilty(switchbutton: UISwitch) -> Void {
    
    let viewHeight:CGFloat = switchbutton.isOn ? 100 : 0.0
    self.testView?.visiblity(gone: !switchbutton.isOn, dimension: viewHeight)
    
    // set visibility for width constraint
    //let viewWidth:CGFloat = switchbutton.isOn ? 300 : 0.0
    //self.testView?.visiblity(gone: !switchbutton.isOn, dimension: viewWidth, attribute: .width)
  
}

Here is result:

enter image description here

Dharman
  • 30,962
  • 25
  • 85
  • 135
Krunal
  • 77,632
  • 48
  • 245
  • 261
23

Maybe you'd prefer this solution

extension UIView {

    enum Visibility {
        case visible
        case invisible
        case gone
    }

    var visibility: Visibility {
        get {
            let constraint = (self.constraints.filter{$0.firstAttribute == .height && $0.constant == 0}.first)
            if let constraint = constraint, constraint.isActive {
                return .gone
            } else {
                return self.isHidden ? .invisible : .visible
            }
        }
        set {
            if self.visibility != newValue {
                self.setVisibility(newValue)
            }
        }
    }

    private func setVisibility(_ visibility: Visibility) {
        let constraint = (self.constraints.filter{$0.firstAttribute == .height && $0.constant == 0}.first)

        switch visibility {
        case .visible:
            constraint?.isActive = false
            self.isHidden = false
            break
        case .invisible:
            constraint?.isActive = false
            self.isHidden = true
            break
        case .gone:
            if let constraint = constraint {
                constraint.isActive = true
            } else {
                let constraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0)
                self.addConstraint(constraint)
                constraint.isActive = true
            }
        }
    }
}

then the usage is:

someView.visibility = .visible
someView.visibility = .invisible
someView.visibility = .gone

edit:

improving capabilities: will work from storyboard (just write: 'visible', 'invisible', 'gone') in the "Visibility State"

all constraints inside view should be less then a 1000

extension UIView {

    enum Visibility: String {
        case visible = "visible"
        case invisible = "invisible"
        case gone = "gone"
    }

    var visibility: Visibility {
        get {
            let constraint = (self.constraints.filter{$0.firstAttribute == .height && $0.constant == 0}.first)
            if let constraint = constraint, constraint.isActive {
                return .gone
            } else {
                return self.isHidden ? .invisible : .visible
            }
        }
        set {
            if self.visibility != newValue {
                self.setVisibility(newValue)
            }
        }
    }

    @IBInspectable
    var visibilityState: String {
        get {
            return self.visibility.rawValue
        }
        set {
            let _visibility = Visibility(rawValue: newValue)!
            self.visibility = _visibility
        }
    }

    private func setVisibility(_ visibility: Visibility) {
        let constraints = self.constraints.filter({$0.firstAttribute == .height && $0.constant == 0 && $0.secondItem == nil && ($0.firstItem as? UIView) == self})
        let constraint = (constraints.first)

        switch visibility {
        case .visible:
            constraint?.isActive = false
            self.isHidden = false
            break
        case .invisible:
            constraint?.isActive = false
            self.isHidden = true
            break
        case .gone:
            self.isHidden = true
            if let constraint = constraint {
                constraint.isActive = true
            } else {
                let constraint = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 0)
                // constraint.priority = UILayoutPriority(rawValue: 999)
                self.addConstraint(constraint)
                constraint.isActive = true
            }
            self.setNeedsLayout()
            self.setNeedsUpdateConstraints()
        }
    }
}
amit
  • 224
  • 2
  • 6
8

You say you want to hide your subview and remove the space it occupies. If you want to avoid messing with constraints, you can try using stack views:

  • Create a UIStackView and setup appropriate constraints between the stack view and its parent view;
  • Add views to the stack view;
  • Now you can toggle the visibility of these views inside the stack view by setting view.isHidden to true or false, and the layout will adjust automatically.
Peter
  • 766
  • 7
  • 8
  • 2
    What If the views don't have constant spacing? – sandpat Sep 25 '18 at 12:56
  • This is the quickest and easiest solution, but maybe is not the best for all cases! @sandpat you just need to set the view inside stack with no fixed size, and two constraints for the top and bottom (or lead and trail) of the views inside this view. – Pelanes Nov 17 '20 at 19:10
3

I believe you are looking for view.isHidden = true. This simply hides the view in place without altering the view hierarchy or constraint mapping. You can subsequently re-show your view . with view.isHidden = false.

Jacob King
  • 6,025
  • 4
  • 27
  • 45