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)
Related
I have a class like this:
class foo: UIView
{
static let childView: UIView
static func addView(onView: UIView)
{
//add childView onto onView
}
static func removeView(onView: UIView)
{
//remove childView from onView
}
}
I want to identify each onView separately, i.e.
If I do like this in a viewController
foo.addView(onView: self.view)
foo.addView(onView: self.view)
It should not remove childView only by calling removeView(onView: self.view) once, in-fact it should also call like this in order to remove child view
removeView(onView: self.view)
removeView(onView: self.view)
For this purpose, I need to identify each UIView separately.
How to identify each onView separately, and the number of times the child view add function is called for it ?
p.s I had tried this with accessibilityIdentifier and set a string tag when add view function is called, and increase the tag if onView has an associated tag with it. And in remove view, I am removing the child view, only if the tag value goes 0.
This somehow achieved what I want, but I am doubting on accessibilityIdentifier, will this disturb some built in functionality.
what apple says is: Here
But don't know what UI Automation Interfaces is, and what is accessibility label.
Edit: (Code inside functions)
static func addView(onView: UIView)
{
if let iden = onView.accessibilityIdentifier,
let identifier = Int(iden)
{
if identifier == 0
{
onView.accessibilityIdentifier = "1"
showHud(onView: onView)
}
else
{
onView.accessibilityIdentifier = "\(identifier + 1)"
}
}
else
{
onView.accessibilityIdentifier = "1"
showHud(onView: onView)
}
}
static func removeView(fromView: UIView)
{
if let iden = fromView.accessibilityIdentifier,
let identifier = Int(iden)
{
if identifier == 1
{
fromView.accessibilityIdentifier = "0"
childView.removeFromSuperview()
}
else
{
if identifier > 1
{
fromView.accessibilityIdentifier = "\(identifier - 1)"
}
}
}
else
{
fromView.accessibilityIdentifier = "0"
childView.removeFromSuperview()
}
}
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())
}
}
Say I have a UIView,
class CleverView: UIView
In the custom class, I want to do this:
func changeWidth() {
let c = ... find my own layout constraint, for "width"
c.constant = 70 * Gameinfo.ImportanceOfEnemyFactor
}
Similarly I wanna be able to "find" like that, the constraint (or I guess, all constraints, there could be more than one) attached to one of the four edges.
So, to look through all the constraints attached to me, and find any width/height ones, or indeed any relevant to a given (say, "left") edge.
Any ideas?
It's perhaps worth noting this question
Please, note that (obviously) I am asking how to do this dynamically/programmatically.
(Yes, you can say "link to the constraint" or "use an ID" - the whole point of the QA is how to find them on the fly and work dynamically.)
If you are new to constraints, note that .constraints just gives you the ends stored "there".
There are really two cases:
Constraints regarding a view's size or relations to descendant views are saved in itself
Constraints between two views are saved in the views' lowest common ancestor
To repeat. For constraints which are between two views. iOS does, in fact, always store them in the lowest common ancestor. Thus, a constraint of a view can always be found by searching all ancestors of the view.
Thus, we need to check the view itself and all its superviews for constraints. One approach could be:
extension UIView {
// retrieves all constraints that mention the view
func getAllConstraints() -> [NSLayoutConstraint] {
// array will contain self and all superviews
var views = [self]
// get all superviews
var view = self
while let superview = view.superview {
views.append(superview)
view = superview
}
// transform views to constraints and filter only those
// constraints that include the view itself
return views.flatMap({ $0.constraints }).filter { constraint in
return constraint.firstItem as? UIView == self ||
constraint.secondItem as? UIView == self
}
}
}
You can apply all kinds of filters after getting all constraints about a view, and I guess that's the most difficult part. Some examples:
extension UIView {
// Example 1: Get all width constraints involving this view
// We could have multiple constraints involving width, e.g.:
// - two different width constraints with the exact same value
// - this view's width equal to another view's width
// - another view's height equal to this view's width (this view mentioned 2nd)
func getWidthConstraints() -> [NSLayoutConstraint] {
return getAllConstraints().filter( {
($0.firstAttribute == .width && $0.firstItem as? UIView == self) ||
($0.secondAttribute == .width && $0.secondItem as? UIView == self)
} )
}
// Example 2: Change width constraint(s) of this view to a specific value
// Make sure that we are looking at an equality constraint (not inequality)
// and that the constraint is not against another view
func changeWidth(to value: CGFloat) {
getAllConstraints().filter( {
$0.firstAttribute == .width &&
$0.relation == .equal &&
$0.secondAttribute == .notAnAttribute
} ).forEach( {$0.constant = value })
}
// Example 3: Change leading constraints only where this view is
// mentioned first. We could also filter leadingMargin, left, or leftMargin
func changeLeading(to value: CGFloat) {
getAllConstraints().filter( {
$0.firstAttribute == .leading &&
$0.firstItem as? UIView == self
}).forEach({$0.constant = value})
}
}
// edit: Enhanced examples and clarified their explanations in comments
I guess you can work with constraints property of UIView. constraints basically returns an array of constraint directly assigned to UIView. It will not be able to get you the constraints held by superview such as leading, trailing, top or bottom but width and height constraints are held by View itself. For superview's constraints, you can loop through superview's constraints. Lets say the clever view has these constraints:
class CleverView: UIView {
func printSuperViewConstriantsCount() {
var c = 0
self.superview?.constraints.forEach({ (constraint) in
guard constraint.secondItem is CleverView || constraint.firstItem is CleverView else {
return
}
c += 1
print(constraint.firstAttribute.toString())
})
print("superview constraints:\(c)")
}
func printSelfConstriantsCount() {
self.constraints.forEach { (constraint) in
return print(constraint.firstAttribute.toString())
}
print("self constraints:\(self.constraints.count)")
}
}
Output:
top
leading
trailing
superview constraints:3
height
self constraints:1
Basically, you can look at NSLayoutConstraint class to get the info out about a particular constraint.
To print the name of constraints, we can use this extension
extension NSLayoutAttribute {
func toString() -> String {
switch self {
case .left:
return "left"
case .right:
return "right"
case .top:
return "top"
case .bottom:
return "bottom"
case .leading:
return "leading"
case .trailing:
return "trailing"
case .width:
return "width"
case .height:
return "height"
case .centerX:
return "centerX"
case .centerY:
return "centerY"
case .lastBaseline:
return "lastBaseline"
case .firstBaseline:
return "firstBaseline"
case .leftMargin:
return "leftMargin"
case .rightMargin:
return "rightMargin"
case .topMargin:
return "topMargin"
case .bottomMargin:
return "bottomMargin"
case .leadingMargin:
return "leadingMargin"
case .trailingMargin:
return "trailingMargin"
case .centerXWithinMargins:
return "centerXWithinMargins"
case .centerYWithinMargins:
return "centerYWithinMargins"
case .notAnAttribute:
return "notAnAttribute"
}
}
}
stakri's answer is OK, but we can do better by using
sequence(first:next:):
extension UIView {
var allConstraints: [NSLayoutConstraint] {
sequence(first: self, next: \.superview)
.flatMap(\.constraints)
.lazy
.filter { constraint in
constraint.firstItem as? UIView == self || constraint.secondItem as? UIView == self
}
}
}
Then, if we check both implementations by swift-benchmark by Google we can see that Sequence implementation is much faster (almost +50k iterations for the ±same time).
running Find All Constraints: Stakri... done! (1778.86 ms)
running Find All Constraints: Sequence... done! (1875.20 ms)
name time std iterations
---------------------------------------------------------------
Find All Constraints.Stakri 3756.000 ns ± 96.67 % 291183
Find All Constraints.Sequence 3727.000 ns ± 117.42 % 342261
Might save someone some typing.......
Based on stakri's bounty-winning answer, here is exactly how to get
all constraints of the type "fractional width of another view"
all constraints of the type "fixed point width"
all constraints of the type "your x position"
So ..
fileprivate extension UIView {
func widthAsPointsConstraints()->[NSLayoutConstraint] {}
func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {}
func xPositionConstraints()->[NSLayoutConstraint]
}
Full code below. Of course, you can do "height" the same way.
So, use them like this...
let cc = someView.widthAsFractionOfAnotherViewConstraints()
for c in cc {
c.changeToNewConstraintWith(multiplier: 0.25)
}
or
let cc = someView.widthAsPointsConstraints()
for c in cc {
c.constant = 150.0
}
Also, at the bottom I pasted in a simple demo code, example output...
Here's the code. V2 ...
fileprivate extension UIView { // experimental
func allConstraints()->[NSLayoutConstraint] {
var views = [self]
var view = self
while let superview = view.superview {
views.append(superview)
view = superview
}
return views.flatMap({ $0.constraints }).filter { constraint in
return constraint.firstItem as? UIView == self ||
constraint.secondItem as? UIView == self
}
}
func widthAsPointsConstraints()->[NSLayoutConstraint] {
return self.allConstraints()
.filter({
( $0.firstItem as? UIView == self && $0.secondItem == nil )
})
.filter({
$0.firstAttribute == .width && $0.secondAttribute == .notAnAttribute
})
}
func widthAsFractionOfAnotherViewConstraints()->[NSLayoutConstraint] {
func _bothviews(_ c: NSLayoutConstraint)->Bool {
if c.firstItem == nil { return false }
if c.secondItem == nil { return false }
if !c.firstItem!.isKind(of: UIView.self) { return false }
if !c.secondItem!.isKind(of: UIView.self) { return false }
return true
}
func _ab(_ c: NSLayoutConstraint)->Bool {
return _bothviews(c)
&& c.firstItem as? UIView == self
&& c.secondItem as? UIView != self
&& c.firstAttribute == .width
}
func _ba(_ c: NSLayoutConstraint)->Bool {
return _bothviews(c)
&& c.firstItem as? UIView != self
&& c.secondItem as? UIView == self
&& c.secondAttribute == .width
}
// note that .relation could be anything: and we don't mind that
return self.allConstraints()
.filter({ _ab($0) || _ba($0) })
}
func xPositionConstraints()->[NSLayoutConstraint] {
return self.allConstraints()
.filter({
return $0.firstAttribute == .centerX || $0.secondAttribute == .centerX
})
}
}
extension NSLayoutConstraint {
// typical routine to "change" multiplier fraction...
#discardableResult
func changeToNewConstraintWith(multiplier:CGFloat) -> NSLayoutConstraint {
//NSLayoutConstraint.deactivate([self])
self.isActive = false
let nc = NSLayoutConstraint(
item: firstItem as Any,
attribute: firstAttribute,
relatedBy: relation,
toItem: secondItem,
attribute: secondAttribute,
multiplier: multiplier,
constant: constant)
nc.priority = priority
nc.shouldBeArchived = self.shouldBeArchived
nc.identifier = self.identifier
//NSLayoutConstraint.activate([nc])
nc.isActive = true
return nc
}
}
Just an example demo...
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
_teste()
delay(5) {
print("changing any 'fraction fo another view' style widths ...\n\n")
let cc = self.animeHolder.widthAsFractionOfAnotherViewConstraints()
for c in cc {
c.changeToNewConstraintWith(multiplier: 0.25)
}
self._teste()
}
delay(10) {
print("changing any 'points' style widths ...\n\n")
let cc = self.animeHolder.widthAsPointsConstraints()
for c in cc {
c.constant = 150.0
}
self._teste()
}
}
func _teste() {
print("\n---- allConstraints")
for c in animeHolder.allConstraints() {
print("\n \(c)")
}
print("\n---- widthAsPointsConstraints")
for c in animeHolder.widthAsPointsConstraints() {
print("\n \(c)\n \(c.multiplier) \(c.constant)")
}
print("\n---- widthAsFractionOfAnotherViewConstraints")
for c in animeHolder.widthAsFractionOfAnotherViewConstraints() {
print("\n \(c)\n \(c.multiplier) \(c.constant)")
}
print("\n----\n")
}
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() })
}
}
I have found a good answer about this problem, look at here How to hide iOS7 UINavigationBar 1px bottom line
but i want to know how to implement it with swift, i've tried in this way
func findHairlineImageViewUnder(view:UIView!) {
if view is UIImageView && view.bounds.size.height <= 1.0 {
return view
}
var subview: UIView
for subview in view.subviews {
var imageView:UIImageView = self.findHairlineImageViewUnder(subview)
if imageView {
return imageView
}
}
}
i can't make it because the compiler told me
'UIView' is not convertible to ()
cannot convert the expression's type 'AnyObject' to type 'UIImageView'
Type 'UIImageView' does not conform to protocol 'BoooleanType'
i know why these errors came out but how can i fix it?
This extension should do it.
extension UINavigationController {
func hairLine(hide hide: Bool) {
//hides hairline at the bottom of the navigationbar
for subview in self.navigationBar.subviews {
if subview.isKindOfClass(UIImageView) {
for hairline in subview.subviews {
if hairline.isKindOfClass(UIImageView) && hairline.bounds.height <= 1.0 {
hairline.hidden = hide
}
}
}
}
}
}
Just call it like this:
navigationController?.hairLine(hide: true)
There are a few problems in your code.
You haven't specified a return type, so the compiler is trying to figure it out for you but is having trouble. You should specify that your return type is UIView?; it should be an optional because you may not be able to find the view and will need to return nil.
You don't need to declare subview before your for loop; the for loop will do it implicitly.
Declaring imageView to a UIImageView will not work without casting. However, you don't really need to make it a UIImageView in the first place and you can let swift deal with it.
By making the change in 1, you can now simplify the inside of your for loop using optional binding and just do if let foundView = self.findHairlineImageViewUnder(subview) { ... }
If you never find the view you're looking for, you never return anything from the function. That's going to cause a compile error in swift, so make sure to return nil at the very end.
Here is a working implementation with the above fixes:
func findHairlineImageViewUnder(view:UIView!) -> UIView? {
if view is UIImageView && view.bounds.size.height <= 1.0 {
return view
}
for subview in view.subviews as [UIView] {
if let foundView = self.findHairlineImageViewUnder(subview) {
return foundView
}
}
return nil
}