Mam UIView, który jest umieszczony na ekranie z kilkoma ograniczeniami. Właścicielem niektórych ograniczeń jest superview, inne należą do innych przodków (np. Być może właściwość widoku UIViewController).
Chcę usunąć wszystkie te stare ograniczenia i umieścić je w nowym miejscu, używając nowych ograniczeń.
Jak mogę to zrobić bez tworzenia IBOutlet dla każdego pojedynczego ograniczenia i konieczności zapamiętywania, który widok jest właścicielem tego ograniczenia?
Aby rozwinąć, naiwnym podejściem byłoby utworzenie grupy IBOutletów dla każdego z ograniczeń, a następnie wywołanie kodu takiego jak:
[viewA removeConstraint:self.myViewsLeftConstraint];
[viewB removeConstraint:self.myViewsTopConstraint];
[viewB removeConstraint:self.myViewsBottomConstraint];
[self.view removeConstraint:self.myViewsRightConstraint];
Problem z tym kodem polega na tym, że nawet w najprostszym przypadku musiałbym stworzyć 2 IBOutlety. W przypadku złożonych układów może to łatwo osiągnąć 4 lub 8 wymaganych IBOutletów. Ponadto musiałbym upewnić się, że moje wezwanie do usunięcia ograniczenia jest wywoływane z właściwego punktu widzenia. Na przykład wyobraź sobie, że myViewsLeftConstraint
jest własnością viewA
. Gdybym przypadkowo zadzwonił [self.view removeConstraint:self.myViewsLeftConstraint]
, nic by się nie stało.
Uwaga: metoda constraintsAffectingLayoutForAxis wygląda obiecująco, ale jest przeznaczona tylko do debugowania.
Aktualizacja: Wiele odpowiedzi jestem otrzymujących kontrakt z self.constraints
, self.superview.constraints
lub jakiegoś wariantu tych. Te rozwiązania nie będą działać, ponieważ te metody zwracają tylko ograniczenia należące do widoku, a nie te, które mają wpływ na widok.
Aby wyjaśnić problem z tymi rozwiązaniami, rozważ następującą hierarchię widoków:
- Dziadek
- Ojciec
- Mnie
- Syn
- Córka
- Brat
- Mnie
- Wujek
- Ojciec
Teraz wyobraź sobie, że tworzymy następujące ograniczenia i zawsze dołączamy je do ich najbliższego wspólnego przodka:
- C0: Me: ten sam top co Son (należący do mnie)
- C1: Me: width = 100 (należące do mnie)
- C2: Ja: taki sam wzrost jak brat (własność ojca)
- C3: Me: ten sam top co Uncle (właściciel Dziadka)
- C4: Ja: to samo co dziadek (własność dziadka)
- C5: Brat: ten sam lewy co ojciec (własność ojca)
- C6: Wujek: to samo lewe co dziadek (własność dziadka)
- C7: Syn: to samo lewe co córka (należące do mnie)
Teraz wyobraź sobie, że chcemy usunąć wszystkie wpływające ograniczenia Me
. Każde właściwe rozwiązanie powinno usunąć [C0,C1,C2,C3,C4]
i nic więcej.
Jeśli użyję self.constraints
(gdzie jaźń jest mną), dostanę [C0,C1,C7]
, ponieważ są to jedyne ograniczenia, które są moją własnością. Oczywiście nie wystarczyłoby to usunąć, ponieważ go brakuje [C2,C3,C4]
. Ponadto usuwa C7
niepotrzebnie.
Jeśli użyję self.superview.constraints
(gdzie ja jest mną), dostanę [C2,C5]
, ponieważ są to ograniczenia należące do Ojca. Oczywiście nie możemy ich wszystkich usunąć, ponieważ C5
jest to całkowicie niezwiązane z Me
.
Jeśli użyję grandfather.constraints
, dostanę [C3,C4,C6]
. Ponownie, nie możemy ich wszystkich usunąć, ponieważ C6
powinny pozostać nienaruszone.
Podejście brutalnej siły polega na zapętleniu nad każdym z przodków widoku (łącznie z nim samym) i sprawdzeniu, firstItem
czy secondItem
sam widok jest, czy też jest; jeśli tak, usuń to ograniczenie. Doprowadzi to do prawidłowego rozwiązania, powrotu [C0,C1,C2,C3,C4]
i tylko tych ograniczeń.
Mam jednak nadzieję, że istnieje bardziej eleganckie rozwiązanie niż konieczność przeglądania całej listy przodków.
źródło
Odpowiedzi:
To podejście zadziałało dla mnie:
@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
Po zakończeniu wykonywanie widoku pozostaje tam, gdzie było, ponieważ tworzy ograniczenia autorezyzacji. Kiedy tego nie robię, widok zazwyczaj znika. Ponadto nie tylko usuwa ograniczenia z superwizji, ale przechodzi całą drogę w górę, ponieważ mogą istnieć ograniczenia wpływające na to w widokach przodków.
Wersja Swift 4
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 } }
źródło
C7
. Jednak powinno być łatwe do naprawienia, jeśli usuniesz[self removeConstraints:self.constraints];
i zmieniszUIView *superview = self.superview;
naUIView *superview = self;
.Jedynym rozwiązaniem, które do tej pory znalazłem, jest usunięcie widoku z jego superviewu:
Wygląda na to, że usuwa wszystkie ograniczenia wpływające na jego układ i jest gotowy do dodania do nadzoru i dołączenia nowych ograniczeń. Jednak spowoduje to niepoprawne usunięcie wszystkich podglądów podrzędnych z hierarchii i usunięcie
[C7]
nieprawidłowo.źródło
Możesz usunąć wszystkie ograniczenia w widoku, wykonując następujące czynności:
self.removeConstraints(self.constraints)
EDYCJA: Aby usunąć ograniczenia wszystkich podglądów podrzędnych, użyj następującego rozszerzenia w Swift:
extension UIView { func clearConstraints() { for subview in self.subviews { subview.clearConstraints() } self.removeConstraints(self.constraints) } }
źródło
self.constraints
zwraca tylko ograniczenia, które sąself
właścicielem, a nie wszystkie ograniczenia, które mają wpływself
.[C6]
, a nieprawidłowo nie usuwało[C0,C1,C2]
.Istnieją dwa sposoby, jak to osiągnąć, zgodnie z dokumentacją programisty Apple
1.
NSLayoutConstraint.deactivateConstraints
// Declaration class func deactivate(_ constraints: [NSLayoutConstraint]) // Usage NSLayoutConstraint.deactivate(yourView.constraints)
2.
UIView.removeConstraints
(Przestarzałe dla> = iOS 8.0)// Declaration func removeConstraints(_ constraints: [NSLayoutConstraint])` // Usage yourView.removeConstraints(yourView.constraints)
Porady
Używanie
Storyboard
s lubXIB
s może być tak uciążliwe przy konfigurowaniu ograniczeń, jak wspomniano w twoim scenariuszu, musisz utworzyć IBOutlety dla każdego z nich, które chcesz usunąć. Mimo to przez większość czasuInterface Builder
stwarza więcej problemów niż rozwiązuje.Dlatego mając bardzo dynamiczną treść i różne stany widoku, sugerowałbym:
Prosty kod
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() }
Bibliografia
źródło
W 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) } }
źródło
if c.firstItem === self
zamiast rzutowaniaas? UIView
i sprawdzenie równości z==
Detale
Rozwiązanie
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) } } }
Stosowanie
print("constraints: \(view.getAllConstraints().count), subviews: \(view.getAllSubviews().count)") view.deactivateAllConstraints()
źródło
Szybkie rozwiązanie:
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 } } }
Ważne jest, aby przejść przez wszystkich rodziców, ponieważ ograniczenia między dwoma elementami są utrzymywane przez wspólnych przodków, więc samo wyczyszczenie superwizji, jak opisano w tej odpowiedzi, nie jest wystarczająco dobre, a później możesz mieć złą niespodziankę.
źródło
Na podstawie wcześniejszych odpowiedzi (szybki 4)
Możesz użyć directConstraints, jeśli nie chcesz przeszukiwać całych hierarchii.
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 } }
źródło
Używam następującej metody, aby usunąć wszystkie ograniczenia z widoku:
plik .h:
+ (void)RemoveContraintsFromView:(UIView*)view removeParentConstraints:(bool)parent removeChildConstraints:(bool)child;
plik .m:
+ (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]]; } }
Możesz również przeczytać ten post na moim blogu, aby lepiej zrozumieć, jak to działa za maską.
Jeśli potrzebujesz bardziej szczegółowej kontroli, zdecydowanie radzę przejść na Masonry , potężną klasę frameworkową, której możesz użyć, gdy potrzebujesz poprawnie obsługiwać ograniczenia programowo.
źródło
Łatwiejszym i skuteczniejszym podejściem jest usunięcie widoku z superView i ponowne dodanie jako podglądu podrzędnego. to powoduje, że wszystkie ograniczenia podglądu są usuwane automagicznie
źródło
Z celem C.
[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]; }
źródło
Możesz użyć czegoś takiego:
[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];
Zasadniczo, to wylicza wszystkie ograniczenia superwizji widoku A i usuwa wszystkie ograniczenia, które są związane z widokiem A.
Następnie druga część usuwa ograniczenia z widoku A przy użyciu tablicy ograniczeń widoku A.
źródło
[C7]
i nieprawidłowo nie usunie[C3,C4]
.(Stan na 31 lipca 2017 r.)
SWIFT 3
self.yourCustomView.removeFromSuperview() self.yourCustomViewParentView.addSubview(self.yourCustomView)
Cel C
[self.yourCustomView removeFromSuperview]; [self.yourCustomViewParentView addSubview:self.yourCustomView];
Jest to najłatwiejszy sposób na szybkie usunięcie wszystkich ograniczeń istniejących w UIView. Po prostu pamiętaj, aby dodać UIView z powrotem z nowymi ograniczeniami lub później nową ramką =)
źródło
Korzystanie z sekwencji wielokrotnego użytku
Postanowiłem podejść do tego w sposób bardziej „wielokrotnego użytku”. Ponieważ znalezienie wszystkich ograniczeń wpływających na widok jest podstawą wszystkich powyższych, zdecydowałem się zaimplementować niestandardową sekwencję, która zwraca je wszystkie za mnie, wraz z widokami będącymi właścicielami.
Pierwszą rzeczą do zrobienia jest zdefiniowanie na przedłużenie
Arrays
oNSLayoutConstraint
tym, że wszystkie elementy wpływające zwrotów widok konkretnego.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 } } }
Następnie używamy tego rozszerzenia w niestandardowej sekwencji, która zwraca wszystkie ograniczenia wpływające na ten widok, wraz z widokami, które faktycznie je posiadają (które mogą znajdować się w dowolnym miejscu w hierarchii widoków)
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) } } } }
Na koniec deklarujemy rozszerzenie,
UIView
aby uwidocznić wszystkie ograniczenia wpływające na to w prostej właściwości, do której można uzyskać dostęp za pomocą prostej składni for-each.extension UIView { var constraintsAffectingView:AllConstraintsSequence { return AllConstraintsSequence(view:self) } }
Teraz możemy iterować wszystkie ograniczenia wpływające na widok i robić z nimi, co chcemy ...
Wymień ich identyfikatory ...
for (constraint, _) in someView.constraintsAffectingView{ print(constraint.identifier ?? "No identifier") }
Dezaktywuj je ...
for (constraint, _) in someView.constraintsAffectingView{ constraint.isActive = false }
Lub usuń je całkowicie ...
for (constraint, owningView) in someView.constraintsAffectingView{ owningView.removeConstraints([constraint]) }
Cieszyć się!
źródło
Szybki
Następujące rozszerzenie UIView usunie wszystkie ograniczenia krawędzi widoku:
extension UIView { func removeAllConstraints() { if let _superview = self.superview { self.removeFromSuperview() _superview.addSubview(self) } } }
źródło
Jest to sposób na wyłączenie wszystkich ograniczeń z określonego widoku
NSLayoutConstraint.deactivate(myView.constraints)
źródło