How can I refresh the contents of a control in a UIStackView? - ios

In Swift 3 (XCode 8.3.3) I have a control in a UIStackView. I have an array of UIImageViews, and loop through the array to populate the stack view at run time:
for voiceIcon in voiceIcons {
let voiceView = UIImageView(image: voiceIcon)
addArrangedSubview(voiceView)
}
These icons will sometimes become disabled (replaced with a new image), so in order to update the control, I have a function to remove all the icons so that I can re-add the appropriate ones (if there's a better way, I'm listening!):
private func resetIconsView() {
for subUIView in self.subviews as [UIView] {
removeArrangedSubview(subUIView)
subUIView.removeFromSuperview()
print("Removing")
}
}
I've also tried
for subUIView in self.subviews as! [UIImageView] { ... }
I get the debug line "Removing" for each of the icons, but they still remain in the control and the UI. I'm new to Swift, so I'm likely not understanding something, what approach should I take?

Try code below:
for view in arrangedSubviews {
view.removeFromSuperview()
}

I am assuming your UIStackView only contains some UIImageView. You can iterate through all the arranged subviews of your stack view and update your image of that imageView. A sample implementation could look like below:
func changeImage() {
for view in self.arrangedSubviews {
if let imgView = view as? UIImageView {
imgView.image = UIImage(named: "taka_icon.png")
}
}
}

I did it with an extension. You have also to remove Constraints if existing. Else this can cause some trouble.
USAGE: myStackView.removeAllArrangedSubviews()
public extension UIStackView {
func removeAllArrangedSubviews() {
let removedSubviews = arrangedSubviews.reduce([]) { (allSubviews, subview) -> [UIView] in
self.removeArrangedSubview(subview)
return allSubviews + [subview]
}
// Deactivate all constraints
NSLayoutConstraint.deactivate(removedSubviews.flatMap({ $0.constraints }))
// Remove the views from self
removedSubviews.forEach({ $0.removeFromSuperview() })
}
}

Related

Programmatically get the custom UIViews embedded inside UIStackView

In my ViewController, I have 12 custom UIViews(named CardView).
I am trying to iterate through the subviews of the ViewController programmatically to find the custom view(CardView) and do some configuration. Following is my code to count the number of CardViews.
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview is CardView{
count = count + 1
}
}
return count
}
However, it is returning me '0' and the reason being my views are embedded inside UIStackViews. I have 3 horizontally aligned stack views inside a vertically aligned one like-
How can I get my CardViews programmatically. Any help would be appreciated.
You can do a few flat maps to flatten your view structure first, then count them:
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews.flatMap { $0.subviews }.flatMap { $0.subviews }{
if subview is CardView{
count = count + 1
}
}
return count
}
But I feel like you are doing things the wrong way around. The number of cards sounds like something in your model. You should have a variable called cardCount and the cards on the screen are supposed to change according to that variable, not the other way around.
You create IBOutlets to each of the horizontal stack views. Then loop through the subviews in the stackviews.
#IBOutlet weak var StackView1: UIStackView!
#IBOutlet weak var StackView2: UIStackView!
#IBOutlet weak var StackView3: UIStackView!
for customView in StackView1.arrangedSubviews
{
// Do task
}
Try this
private func cardCount() -> Int
{
var count = 0
for subview in self.view.subviews
{
if let stackView: UIStackView = subview as? UIStackView
{
let arrangedViews = stackView.arrangedSubviews
for cardView in arrangedViews
{
if cardView is CardView
{
count = count + 1
}
}
}
}
return count
}
From the pictorial diagram you shared, it seems the StackView is the first child. Whenever you get the subviews, only the first child views are returned. So self.view.subviews would result in only one UIStackView. Give a tag to each stack view. Then, in code :
private func cardCount()->Int{
var count = 0
for subview in self.view.subviews{
if subview.tag == 10 { // parent stack view
for subviewStack in subview { // Get subviews of parent stackview
if subviewStack.tag == 11 { // First stack view child
for subViewSubStack in subviewStack.subviews { // Get card views in this stack view
// Apply your count logic
}
}
}
}
}
return count
}
I have written only the first condition. You might add others.
Having said this, I won't say this is the most optimum solution. This can and should be refactored.
Create an extension method like this. It will count all CardView instances in the view controller.
extension UIView {
func cardCount() -> Int {
switch self {
case let self as CardView:
return 1
case let self as UIStackView:
return self.arrangedSubviews.reduce(0, { $0 + $1.cardCount() })
default:
return self.subviews.reduce(0, { $0 + $1.cardCount() })
}
}
}
and call this method in viewDidLoad
class ViewControllerd: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(self.view.cardCount())
}
}

How can return UIView views along with its subviews in swift?

I want to create a stack of rounded circle views like this.
UIView stack
I want to return this whole stack of UIViews at once. So I tried in this way.
open func setupCirclestack(parentFrame:CGRect)->UIView
{
let arrayColor=[UIColor.yellow,UIColor.blue,UIColor.red]
let baseCircle=Circle.init(frame: parentFrame)
baseCircle.backgroundColor=UIColor.purple
var parentview=baseCircle
// var existingFrame=baseCircle.frame
for i in 0...2//<CircleValues().numberOfCircles-1
{
let circle=self.getInnerCircle(currentFrame: parentFrame)
circle.backgroundColor=arrayColor[i]
parentview.addSubview(circle)
parentview=circle as! Circle
//existingFrame=circle.frame
}
return parentview
}
func getInnerCircle(currentFrame:CGRect)->UIView
{
CircleValues.sharedInstance.radius=CircleValues.sharedInstance.radius-30
print("New Radius------\(CircleValues.sharedInstance.radius)")
let circle=Circle.init(frame: currentFrame)
return circle
}
But I can get only the last (inner most view) view. How can I return the whole stack of UIViews from this method
It is because your all the frames for all the circles are same as parentFrame.
So, your all views are adding but you can only able to see last view as it is overlaps other views!
You have to decrease your frame size for every iteration of your for loop for every child view you are adding!
for i in 0...2//<CircleValues().numberOfCircles-1
{
let circle=self.getInnerCircle(currentFrame: parentFrame) // decrease size(height and width) here every time to achieve result attached in screenshot in your question
circle.backgroundColor=arrayColor[i]
parentview.addSubview(circle)
parentview=circle as! Circle
//existingFrame=circle.frame
}
You reasign a parentView variable at the end of for loop, So it will replace existing object of parentView in which you added the circle as a subview. Therefore it will return a last circle object which is stored in parentView.
for i in 0...2//<CircleValues().numberOfCircles-1
{
let circle=self.getInnerCircle(currentFrame: parentFrame)
circle.backgroundColor=arrayColor[i]
parentview.addSubview(circle)
//parentview=circle as! Circle
//existingFrame=circle.frame
}
If you want to return views inside your parent view you need to change your function to something like this:
open func setupCirclestack(parentFrame:CGRect)->UIView
{
let arrayColor=[UIColor.yellow,UIColor.blue,UIColor.red]
let baseCircle=Circle.init(frame: parentFrame)
baseCircle.backgroundColor=UIColor.purple
var parentview=baseCircle
// var existingFrame=baseCircle.frame
for i in 0...2//<CircleValues().numberOfCircles-1
{
let circle=self.getInnerCircle(currentFrame: parentFrame)
circle.backgroundColor=arrayColor[i]
parentview.addSubview(circle)
}
return parentview
}
But if you need to return array of views, try this:
open func setupCirclestack(parentFrame:CGRect)->[UIView]
{
let arrayColor = [UIColor.yellow,UIColor.blue,UIColor.red]
let baseCircle = Circle.init(frame: parentFrame)
baseCircle.backgroundColor=UIColor.purple
var circleArray = [Circle]()
for i in 0...2
{
let circle = self.getInnerCircle(currentFrame: parentFrame)
circle.backgroundColor = arrayColor[i]
circleArray.append(circle)
}
return circleArray
}

How to loop through all UIButtons in my Swift view?

How would I loop through all UIButtons in my view in Swift? I would want to set all the titles to "", but my for-loop in Swift is giving an error.
for btns in self.view as [UIButton] {
// set the title to ""
}
This code should work:
for view in self.view.subviews as [UIView] {
if let btn = view as? UIButton {
btn.setTitleForAllStates("")
}
}
You need to iterate through the subViews array.
Shortened and updated for Swift 3 & 4
for case let button as UIButton in self.view.subviews {
button.setTitleForAllStates("")
}
Looping over subview works, but it's sometimes a little ugly, and has other issues.
If you need to loop over some specific buttons, for example to add corner radius or change tint or background color, you can use an array of IBOutlets and then loop over that.
var buttons = [SkipBtn, AllowBtn]
for button in buttons as! [UIButton] {
button.layer.cornerRadius = 5
}
Swift 4:
let subviewButtons = self.view.subviews.filter({$0.isKind(of: UIButton.self)})
for button in subviewButtons {
//do something
}
To add some context for a common use case, suppose the buttons were in a scroll view and you wanted to highlight the tapped button and de-highlight the other buttons. In this situation, you would direct all buttons to one action method:
#objc private func buttonAction(_ button: UIButton) {
for case let b as UIButton in view.scrollView.subviews {
if b == button {
b.setTitleColor(UIColor.green, for: []) // highlight
} else {
b.setTitleColor(UIColor.black, for: []) // de-highlight
}
}
}
This code seems to be quite useful for iterating over any object within a view, just change UIButton for any other subview type such as UIView or UIImageView, etc.
let filteredSubviews = self.view.subviews.filter({
$0.isKindOfClass(UIButton)})
for view in filteredSubviews {
//Do something
}
Used some of the offered questions out there and created my own. I believe is the most efficient when you want to programmatically set up the title of various UIButtons(in my case I am building a quiz)
By randomising my array list and with just a for loop I printing the item at index to the button title
for view in self.viewForButtons.subviews{
if view.isKindOfClass(UIButton)
{
let button : UIButton = view as! UIButton
button.setTitle("item[i]", forState: .Normal)
}
}
If you have UIView's within self.view then you need to loop through the subviews while searching for UIButton. Using the accepted answer, I made this little function to do so:
Swift 4 + :
func findButton(`in` view: UIView){
for view in view.subviews as [UIView] {
if let button = view as? UIButton {
// Do something with 'button'
}else{
// Loop through subview looking for buttons
findButton(in: view)
}
}
}
Usage:
override func viewDidLoad() {
findButton(in: self.view)
}
Hope this helps!
Here's a short way in Swift if you know the subview only has buttons:
myView.subviews.map {
($0 as? UIButton)!.enabled = false
}

Find UILabel in UIView in Swift

I'm trying to find my UILabels in my superview of my UIViewControllers.
This is my code:
func watch(startTime:String, endTime:String) {
if superview == nil {println("NightWatcher: No viewcontroller specified");return}
listSubviewsOfView(self.superview!)
}
func listSubviewsOfView(view: UIView) {
var subviews = view.subviews
if (subviews.count == 0) { return }
view.backgroundColor = UIColor.blackColor()
for subview in subviews {
if subview.isKindOfClass(UILabel) {
// do something with label..
}
self.listSubviewsOfView(subview as UIView)
}
}
This is how it is recommended to in Objective-C, but in Swift I get nothing but UIViews and CALayer. I definitely have UILabels in the view that is supplied to this method. What am I missing?
The call in my UIViewController:
NightWatcher(view: self.view).watch("21:00", endTime: "08:30") // still working on
Here's a version that will return an Array of all the UILabel views in whatever view you pass in:
func getLabelsInView(view: UIView) -> [UILabel] {
var results = [UILabel]()
for subview in view.subviews as [UIView] {
if let labelView = subview as? UILabel {
results += [labelView]
} else {
results += getLabelsInView(view: subview)
}
}
return results
}
Then you can iterate over them to do whatever you'd like:
override func viewDidLoad() {
super.viewDidLoad()
let labels = getLabelsInView(self.view)
for label in labels {
println(label.text)
}
}
Using functional programming concepts you can achieve this much easier.
let labels = self.view.subviews.flatMap { $0 as? UILabel }
for label in labels {
//Do something with label
}
Swift 4
Adepting mKane's answer you can use this code:
let labels = self.view.subviews.compactMap { $0 as? UILabel }
for label in labels {
// do whatever
}
You could set a tag to your UILabel in the Storyboard or programmatically using:
myLabel.tag = 1234
Then, to find it use:
let myLabel = view.viewWithTag(1234)

Remove all constraints affecting a UIView

I have a UIView which is placed on the screen via several constraints. Some of the constraints are owned by the superview, others are owned by other ancestors (e.g. perhaps the view property of a UIViewController).
I want to remove all of these old constraints, and place it somewhere new using new constraints.
How can I do this without creating an IBOutlet for every single constraint and having to remember which view owns said constraint?
To elaborate, the naive approach would be to create a bunch of IBOutlets for each of the constraints, and would then involve calling code such as:
[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];
The problem with this code is that even in the simplest case, I would need to create 2 IBOutlets. For complex layouts, this could easily reach 4 or 8 required IBOutlets. Furthermore, I would need to ensure that my call to remove the constraint is being called on the proper view. For example, imagine that myViewsLeftConstraint is owned by viewA. If I were to accidentally call [self.view removeConstraint:self.myViewsLeftConstraint], nothing would happen.
Note: The method constraintsAffectingLayoutForAxis looks promising, but is intended for debugging purposes only.
Update: Many of the answers I am receiving deal with self.constraints, self.superview.constraints, or some variant of those. These solutions won't work since those methods return only the constraints owned by the view, not the ones affecting the view.
To clarify the problem with these solutions, consider this view hierarchy:
Grandfather
Father
Me
Son
Daughter
Brother
Uncle
Now imagine we create the following constraints, and always attach them to their nearest common ancestor:
C0: Me: same top as Son (owned by Me)
C1: Me: width = 100 (owned by Me)
C2: Me: same height as Brother (owned by Father)
C3: Me: same top as Uncle (owned by Grandfather)
C4: Me: same left as Grandfather (owned by Grandfather)
C5: Brother: same left as Father (owned by Father)
C6: Uncle: same left as Grandfather (owned by Grandfather)
C7: Son: same left as Daughter (owned by Me)
Now imagine we want to remove all constraints affecting Me. Any proper solution should remove [C0,C1,C2,C3,C4] and nothing else.
If I use self.constraints (where self is Me), I will get [C0,C1,C7], since those are the only constraints owned by Me. Obviously it wouldn't be enough to remove this since it is missing [C2,C3,C4]. Furthermore, it is removing C7 unnecessarily.
If I use self.superview.constraints (where self is Me), I will get [C2,C5], since those are the constraints owned by Father. Obviously we cannot remove all these since C5 is completely unrelated to Me.
If I use grandfather.constraints, I will get [C3,C4,C6]. Again, we cannot remove all of these since C6 should remain intact.
The brute force approach is to loop over each of the view's ancestors (including itself), and seeing if firstItem or secondItem are the view itself; if so, remove that constraint. This will lead to a correct solution, returning [C0,C1,C2,C3,C4], and only those constraints.
However, I'm hoping there is a more elegant solution than having to loop through the entire list of ancestors.
This approach worked for me:
#interface UIView (RemoveConstraints)
- (void)removeAllConstraints;
#end
#implementation UIView (RemoveConstraints)
- (void)removeAllConstraints
{
UIView *superview = self.superview;
while (superview != nil) {
for (NSLayoutConstraint *c in superview.constraints) {
if (c.firstItem == self || c.secondItem == self) {
[superview removeConstraint:c];
}
}
superview = superview.superview;
}
[self removeConstraints:self.constraints];
self.translatesAutoresizingMaskIntoConstraints = YES;
}
#end
After it's done executing your view remains where it was because it creates autoresizing constraints. When I don't do this the view usually disappears. Additionally, it doesn't just remove constraints from superview but traversing all the way up as there may be constraints affecting it in ancestor views.
Swift 4 Version
extension UIView {
public func removeAllConstraints() {
var _superview = self.superview
while let superview = _superview {
for constraint in superview.constraints {
if let first = constraint.firstItem as? UIView, first == self {
superview.removeConstraint(constraint)
}
if let second = constraint.secondItem as? UIView, second == self {
superview.removeConstraint(constraint)
}
}
_superview = superview.superview
}
self.removeConstraints(self.constraints)
self.translatesAutoresizingMaskIntoConstraints = true
}
}
The only solution I have found so far is to remove the view from its superview:
[view removeFromSuperview]
This looks like it removes all constraints affecting its layout and is ready to be added to a superview and have new constraints attached. However, it will incorrectly remove any subviews from the hierarchy as well, and get rid of [C7] incorrectly.
You can remove all constraints in a view by doing this:
self.removeConstraints(self.constraints)
EDIT: To remove the constraints of all subviews, use the following extension in Swift:
extension UIView {
func clearConstraints() {
for subview in self.subviews {
subview.clearConstraints()
}
self.removeConstraints(self.constraints)
}
}
There are two ways of on how to achieve that according to Apple Developer Documentation
1. NSLayoutConstraint.deactivateConstraints
This is a convenience method that provides an easy way to deactivate a
set of constraints with one call. The effect of this method is the
same as setting the isActive property of each constraint to false.
Typically, using this method is more efficient than deactivating each
constraint individually.
// Declaration
class func deactivate(_ constraints: [NSLayoutConstraint])
// Usage
NSLayoutConstraint.deactivate(yourView.constraints)
2. UIView.removeConstraints (Deprecated for >= iOS 8.0)
When developing for iOS 8.0 or later, use the NSLayoutConstraint
class’s deactivateConstraints: method instead of calling the
removeConstraints: method directly. The deactivateConstraints: method
automatically removes the constraints from the correct views.
// Declaration
func removeConstraints(_ constraints: [NSLayoutConstraint])`
// Usage
yourView.removeConstraints(yourView.constraints)
Tips
Using Storyboards or XIBs can be such a pain at configuring the constraints as mentioned on your scenario, you have to create IBOutlets for each ones you want to remove. Even so, most of the time Interface Builder creates more trouble than it solves.
Therefore when having very dynamic content and different states of the view, I would suggest:
Creating your views programmatically
Layout them and using NSLayoutAnchor
Append each constraint that might get removed later to an array
Clear them every time before applying the new state
Simple Code
private var customConstraints = [NSLayoutConstraint]()
private func activate(constraints: [NSLayoutConstraint]) {
customConstraints.append(contentsOf: constraints)
customConstraints.forEach { $0.isActive = true }
}
private func clearConstraints() {
customConstraints.forEach { $0.isActive = false }
customConstraints.removeAll()
}
private func updateViewState() {
clearConstraints()
let constraints = [
view.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
view.trailingAnchor.constraint(equalTo: parentView.trailingAnchor),
view.topAnchor.constraint(equalTo: parentView.topAnchor),
view.bottomAnchor.constraint(equalTo: parentView.bottomAnchor)
]
activate(constraints: constraints)
view.layoutIfNeeded()
}
References
NSLayoutConstraint
UIView
In Swift:
import UIKit
extension UIView {
/**
Removes all constrains for this view
*/
func removeConstraints() {
let constraints = self.superview?.constraints.filter{
$0.firstItem as? UIView == self || $0.secondItem as? UIView == self
} ?? []
self.superview?.removeConstraints(constraints)
self.removeConstraints(self.constraints)
}
}
Details
Xcode 10.2.1 (10E1001), Swift 5
Solution
import UIKit
extension UIView {
func removeConstraints() { removeConstraints(constraints) }
func deactivateAllConstraints() { NSLayoutConstraint.deactivate(getAllConstraints()) }
func getAllSubviews() -> [UIView] { return UIView.getAllSubviews(view: self) }
func getAllConstraints() -> [NSLayoutConstraint] {
var subviewsConstraints = getAllSubviews().flatMap { $0.constraints }
if let superview = self.superview {
subviewsConstraints += superview.constraints.compactMap { (constraint) -> NSLayoutConstraint? in
if let view = constraint.firstItem as? UIView, view == self { return constraint }
return nil
}
}
return subviewsConstraints + constraints
}
class func getAllSubviews(view: UIView) -> [UIView] {
return view.subviews.flatMap { [$0] + getAllSubviews(view: $0) }
}
}
Usage
print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)")
view.deactivateAllConstraints()
The easier and efficient approach is to remove the view from superView and re add as subview again.
this causes all the subview constraints get removed automagically.😉
Swift
Following UIView Extension will remove all Edge constraints of a view:
extension UIView {
func removeAllConstraints() {
if let _superview = self.superview {
self.removeFromSuperview()
_superview.addSubview(self)
}
}
}
A Swift solution:
extension UIView {
func removeAllConstraints() {
var view: UIView? = self
while let currentView = view {
currentView.removeConstraints(currentView.constraints.filter {
return $0.firstItem as? UIView == self || $0.secondItem as? UIView == self
})
view = view?.superview
}
}
}
It's important to go through all the parents, since the constraints between two elements are holds by the common ancestors, so just clearing the superview as detailed in this answer is not good enough, and you might end up having bad surprise later on.
Based on previous answers (swift 4)
You can use immediateConstraints when you don't want to crawl entire hierarchies.
extension UIView {
/**
* Deactivates immediate constraints that target this view (self + superview)
*/
func deactivateImmediateConstraints(){
NSLayoutConstraint.deactivate(self.immediateConstraints)
}
/**
* Deactivates all constrains that target this view
*/
func deactiveAllConstraints(){
NSLayoutConstraint.deactivate(self.allConstraints)
}
/**
* Gets self.constraints + superview?.constraints for this particular view
*/
var immediateConstraints:[NSLayoutConstraint]{
let constraints = self.superview?.constraints.filter{
$0.firstItem as? UIView === self || $0.secondItem as? UIView === self
} ?? []
return self.constraints + constraints
}
/**
* Crawls up superview hierarchy and gets all constraints that affect this view
*/
var allConstraints:[NSLayoutConstraint] {
var view: UIView? = self
var constraints:[NSLayoutConstraint] = []
while let currentView = view {
constraints += currentView.constraints.filter {
return $0.firstItem as? UIView === self || $0.secondItem as? UIView === self
}
view = view?.superview
}
return constraints
}
}
I use the following method to remove all constraints from a view:
.h file:
+ (void)RemoveContraintsFromView:(UIView*)view
removeParentConstraints:(bool)parent
removeChildConstraints:(bool)child;
.m file:
+ (void)RemoveContraintsFromView:(UIView *)view
removeParentConstraints:(bool)parent
removeChildConstraints:(bool)child
{
if (parent) {
// Remove constraints between view and its parent.
UIView *superview = view.superview;
[view removeFromSuperview];
[superview addSubview:view];
}
if (child) {
// Remove constraints between view and its children.
[view removeConstraints:[view constraints]];
}
}
You can also read this post on my blog to better understand how it works behind the hood.
If you need more granular control, I'd strongly advise switching to Masonry, a powerful framework class you could use whenever you need to properly handle constraints programmatically.
With objectiveC
[self.superview.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
if (constraint.firstItem == self || constraint.secondItem == self) {
[self.superview removeConstraint:constraint];
}
}];
[self removeConstraints:self.constraints];
}
You could use something like this:
[viewA.superview.constraints enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLayoutConstraint *constraint = (NSLayoutConstraint *)obj;
if (constraint.firstItem == viewA || constraint.secondItem == viewA) {
[viewA.superview removeConstraint:constraint];
}
}];
[viewA removeConstraints:viewA.constraints];
Basically, this is enumerates over all the constraints on the superview of viewA and removes all of the constraints that are related to viewA.
Then, the second part removes the constraints from viewA using the array of viewA's constraints.
(As of July 31, 2017)
SWIFT 3
self.yourCustomView.removeFromSuperview()
self.yourCustomViewParentView.addSubview(self.yourCustomView)
Objective C
[self.yourCustomView removeFromSuperview];
[self.yourCustomViewParentView addSubview:self.yourCustomView];
This is the easiest way to quickly remove all constraints that exist on a UIView. Just be sure to add the UIView back with it's new constraints or new frame afterwards =)
Using a Reusable Sequence
I decided to approach this in a more 'reusable' way. Since finding all constraints affecting a view is the basis for all of the above, I decided to implement a custom sequence that returns them all for me, along with the owning views.
First thing to do is define an extension on Arrays of NSLayoutConstraint that returns all elements affecting a specific view.
public extension Array where Element == NSLayoutConstraint {
func affectingView(_ targetView:UIView) -> [NSLayoutConstraint] {
return self.filter{
if let firstView = $0.firstItem as? UIView,
firstView == targetView {
return true
}
if let secondView = $0.secondItem as? UIView,
secondView == targetView {
return true
}
return false
}
}
}
We then use that extension in a custom sequence that returns all constraints affecting that view, along with the views that actually own them (which can be anywhere up the view hierarchy)
public struct AllConstraintsSequence : Sequence {
public init(view:UIView){
self.view = view
}
public let view:UIView
public func makeIterator() -> Iterator {
return Iterator(view:view)
}
public struct Iterator : IteratorProtocol {
public typealias Element = (constraint:NSLayoutConstraint, owningView:UIView)
init(view:UIView){
targetView = view
currentView = view
currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
}
private let targetView : UIView
private var currentView : UIView
private var currentViewConstraintsAffectingTargetView:[NSLayoutConstraint] = []
private var nextConstraintIndex = 0
mutating public func next() -> Element? {
while(true){
if nextConstraintIndex < currentViewConstraintsAffectingTargetView.count {
defer{nextConstraintIndex += 1}
return (currentViewConstraintsAffectingTargetView[nextConstraintIndex], currentView)
}
nextConstraintIndex = 0
guard let superview = currentView.superview else { return nil }
self.currentView = superview
self.currentViewConstraintsAffectingTargetView = currentView.constraints.affectingView(targetView)
}
}
}
}
Finally we declare an extension on UIView to expose all the constraints affecting it in a simple property that you can access with a simple for-each syntax.
extension UIView {
var constraintsAffectingView:AllConstraintsSequence {
return AllConstraintsSequence(view:self)
}
}
Now we can iterate all constraints affecting a view and do what we want with them...
List their identifiers...
for (constraint, _) in someView.constraintsAffectingView{
print(constraint.identifier ?? "No identifier")
}
Deactivate them...
for (constraint, _) in someView.constraintsAffectingView{
constraint.isActive = false
}
Or remove them entirely...
for (constraint, owningView) in someView.constraintsAffectingView{
owningView.removeConstraints([constraint])
}
Enjoy!
This is the way to disable all constraints from a specific view
NSLayoutConstraint.deactivate(myView.constraints)

Resources