Einschränkung für das automatische Layout unter CALayer IOS


78

Hallo, ich entwickle eine iPhone-Anwendung, in der ich versucht habe, einen Seitenrand für edittext festzulegen. Ich habe das folgendermaßen gemacht:

 int borderWidth = 1;
CALayer *leftBorder = [CALayer layer];

leftBorder.borderColor = [UIColor whiteColor].CGColor;
leftBorder.borderWidth = borderWidth;

leftBorder.frame = CGRectMake(0, textField.frame.size.height - borderWidth, textField
                              .frame.size.width, borderWidth);
[textField.layer addSublayer:leftBorder];

Ich habe meinem Edittext in IB einige Einschränkungen auferlegt, damit beim Drehen meines Geräts die Breite des Textfelds entsprechend angepasst wird. Mein Problem ist, dass die Breite von edittext angepasst wird und nicht die Breite von CALayer, die ich für meinen Bearbeitungstext festgelegt habe. Ich denke, ich muss auch einige Einschränkungen für meinen CALayer-Gegenstand festlegen. Aber ich weiß nicht, wie ich das machen soll. Weiß jemand davon? Brauchen Sie Hilfe. Vielen Dank.


1
nicht möglich. Einschränkungen funktionieren nur mit UIView
Max MacLeod

Antworten:


122

Das gesamte Autoresizing-Geschäft ist ansichtsspezifisch. Ebenen werden nicht automatisch angepasst.

Was Sie tun müssen - im Code - ist, die Größe der Ebene selbst zu ändern

z.B

in einem viewController würden Sie tun

- (void) viewDidLayoutSubviews {
  [super viewDidLayoutSubviews]; //if you want superclass's behaviour... 
  // resize your layers based on the view's new frame
  self.editViewBorderLayer.frame = self.editView.bounds;
}

oder in einer benutzerdefinierten UIView, die Sie verwenden könnten

- (void)layoutSubviews {
  [super layoutSubviews]; //if you want superclass's behaviour...  (and lay outing of children)
  // resize your layers based on the view's new frame
  layer.frame = self.bounds;
}

1
Wenn Sie layoutSubviews verwenden, sollten Sie es auf super
rmp251

@ rmp251 Weder Dokumente noch Header-Dateien sagen dies und es scheint seltsam. Ich meine, ich würde nur das Standardverhalten beibehalten - Auto-Layout (auf ios51 +). Vielleicht ist es das, was man will, aber es ist nicht immer richtig ... also kein AFAIK
Daij-Djan

21
Diese Lösung funktioniert nicht, wenn die Größe der Ansicht, der der Layer gehört, über Autolayout-Einschränkungen angepasst wird. In diesem Fall spiegeln die Grenzen und Rahmeneigenschaften nicht die tatsächlichen Grenzen / Rahmen wider, die durch die Einschränkungen bestimmt werden. Irgendwelche Vorschläge?
Alfie Hanssen

3
@AlfieHanssen sie spiegeln den tatsächlichen Rahmen und die Grenzen wider. LayoutSubviews macht das. Sie können auch nach ViewDidAppear ausgelöst werden. Rufen Sie auch layoutIfNeeded, InvalidateIntrinsic usw. auf
Nick Turner

2
Dies funktioniert auch nicht, wenn Sie Autolayout-Einschränkungen mit layoutIfNeeded für eine Ansicht animieren, die einige Unterebenen enthält, die den Rahmen der Ansicht widerspiegeln sollen.
dvdblk

9

In Swift 5können Sie die folgende Lösung ausprobieren:

Verwenden Sie KVO für den unten angegebenen Pfad "YourView.bounds".

self.addObserver(self, forKeyPath: "YourView.bounds", options: .new, context: nil)

Behandeln Sie es dann wie unten angegeben.

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if keyPath == "YourView.bounds" {
            YourLayer.frame = YourView.bounds
            return
        }
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }

Mehr Infos dazu hier


1
Dies hat hervorragend funktioniert und ich denke, es ist der beste Ansatz, mit "Autolayout" zu arbeiten, anstatt es in * Layoutsubviews
Salil Junior

Die URL in der Antwort ist fehlerhaft.
USA

7

Nach dieser Antwort layoutSubviewswird nicht in allen benötigten Fällen angerufen. Ich habe diese Delegatenmethode für effektiver befunden:

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if(self != nil) {
        [self.layer addSublayer:self.mySublayer];
    }
    return self;
}

- (void)layoutSublayersOfLayer:(CALayer*)layer
{
    self.mySublayer.frame = self.bounds;
}

5

Ich habe die Methode layoutSubviews meiner benutzerdefinierten Ansicht implementiert. Bei dieser Methode aktualisiere ich einfach den Frame jeder Unterschicht, um ihn an die aktuellen Grenzen der Ebene meiner Unteransicht anzupassen.

-(void)layoutSubviews{
      sublayer1.frame = self.layer.bounds;
}

Beste Lösung !
Mc.Lover

8
Rufen Sie mindestens super
Daij-Djan

5

Meine Lösung, wenn ich mit gestricheltem Rand erstelle

class DashedBorderView: UIView {

   let dashedBorder = CAShapeLayer()

   override init(frame: CGRect) {
       super.init(frame: frame)
       commonInit()
   }

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

   private func commonInit() {
       //custom initialization
       dashedBorder.strokeColor = UIColor.red.cgColor
       dashedBorder.lineDashPattern = [2, 2]
       dashedBorder.frame = self.bounds
       dashedBorder.fillColor = nil
       dashedBorder.cornerRadius = 2
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       self.layer.addSublayer(dashedBorder)
   }

   override func layoutSublayers(of layer: CALayer) {
       super.layoutSublayers(of: layer)
       dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
       dashedBorder.frame = self.bounds
   }
}

Lief wie am Schnürchen.
user246392

1

Swift 3.x KVO Solution (Aktualisierte Antwort von @ arango_86)

Beobachter hinzufügen

self.addObserver(
    self, 
    forKeyPath: "<YOUR_VIEW>.bounds", 
    options: .new, 
    context: nil
)

Werte beachten

override open func observeValue(
    forKeyPath keyPath: String?, 
    of object: Any?, 
    change: [NSKeyValueChangeKey : Any]?, 
    context: UnsafeMutableRawPointer?
) {
    if (keyPath == "<YOUR_VIEW.bounds") {
      updateDashedShapeLayerFrame()
      return
    }

    super.observeValue(
        forKeyPath: keyPath, 
        of: object, 
        change: change, 
        context: context
    )
}

1

Wenn ich KVO anwenden muss, mache ich es lieber mit RxSwift (Nur wenn ich es für mehr Dinge verwende, füge diese Bibliothek nicht nur dafür hinzu.)

Sie können KVO auch mit dieser Bibliothek anwenden, oder in der, viewDidLayoutSubviewsaber ich habe damit bessere Ergebnisse erzielt.

  view.rx.observe(CGRect.self, #keyPath(UIView.bounds))
        .subscribe(onNext: { [weak self] in
            guard let bounds = $0 else { return }
            self?.YourLayer.frame = bounds
        })
        .disposed(by: disposeBag)

0

Riffing off arango_86s Antwort - Wenn Sie den KVO-Fix in Ihrer eigenen UIView-Unterklasse anwenden, ist es "schneller", dies zu überschreiben boundsund didSetwie folgt zu verwenden :

override var bounds: CGRect {
    didSet {
        layer.frame = bounds
    }
}

-1

Ich hatte ein ähnliches Problem - ich musste den Rahmen eines 'CALayer' festlegen, wenn ich das automatische Layout mit Ansichten verwendete (im Code, nicht IB ).

In meinem Fall hatte ich eine leicht verschlungene Hierarchie mit einem Ansichts-Controller innerhalb eines Ansichts-Controllers. Ich kam zu dieser SO-Frage und untersuchte den Ansatz der Verwendung viewDidLayoutSubviews. Das hat nicht funktioniert. Nur für den Fall, dass Ihre Situation meiner ähnlich ist, habe ich Folgendes gefunden ...

Überblick

Ich wollte den Rahmen für den CAGradientLayervon UIViewmir als Unteransicht positionierten Rahmen innerhalb eines unter UIViewControllerVerwendung von automatischen Layoutbeschränkungen festlegen .

Rufen Sie die Unteransicht gradientViewund den Ansichtscontroller auf child_viewController.

child_viewControllerwar ein View Controller, den ich als eine Art wiederverwendbare Komponente eingerichtet hatte. Das viewof child_viewControllerwurde also zu einem übergeordneten View-Controller zusammengesetzt - nennen Sie das parent_viewController.

Als viewDidLayoutSubviewsof child_viewControlleraufgerufen wurde, war das frameof gradientViewnoch nicht eingestellt.

(An dieser Stelle würde ich empfehlen, einige NSLogAnweisungen zu verteilen, um ein Gefühl für die Reihenfolge der Erstellung von Ansichten in Ihrer Hierarchie usw. zu bekommen.)

Also habe ich es weiter versucht viewDidAppear. Aufgrund der Verschachtelung von child_viewControllerfand ich viewDidAppearjedoch, dass nicht aufgerufen wurde.

(Siehe diese SO-Frage: viewWillAppear, viewDidAppear wird nicht aufgerufen, nicht ausgelöst ).

Meine aktuelle Lösung

Ich habe hinzugefügt , viewDidAppearin parent_viewControllerund von dort Ich rufe viewDidAppearin child_viewController.

Für das anfängliche Laden brauche ich, viewDidAppearda erst wenn dies aufgerufen wird child_viewController, alle Unteransichten ihre Frames gesetzt haben. Ich kann dann den Rahmen für die CAGradientLayer...

Ich habe gesagt, dass dies meine aktuelle Lösung ist, weil ich damit nicht besonders zufrieden bin.

Nach dem erstmaligen Einstellen des Rahmens von CAGradientLayer- kann dieser Rahmen ungültig werden, wenn sich das Layout ändert - z. B. durch Drehen des Geräts.

Um damit umzugehen, benutze ich viewDidLayoutSubviewsin child_viewController- um den Rahmen des CAGradientLayerInneren gradientViewkorrekt zu halten.

Es funktioniert, fühlt sich aber nicht gut an. (Gibt es einen besseren Weg?)


1
Im Allgemeinen sollten Sie die Methoden für das Erscheinungsbild / den Lebenszyklus der Ansicht nicht manuell aufrufen. Daij-Djan hat in diesem speziellen Fall die richtige Idee.
Sami Samhuri
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.