Dodaj obramowanie poza UIView (zamiast wewnątrz)

83

Jeśli dodasz obramowanie widoku za pomocą kodu w widoku, takim jak

self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;

obramowanie jest dodawane wewnątrz widoku w następujący sposób: wprowadź opis obrazu tutaj

widok właściwy jest widokiem oryginalnym, jak widać, czarny obszar widoku granicznego jest mniejszy niż widok oryginalny. ale to, co chcę dostać to poza granicę oryginalnego widoku, tak: wprowadź opis obrazu tutaj. czarny obszar jest taki sam jak oryginalny, jak mogę to zaimplementować?

remykity
źródło

Odpowiedzi:

102

Niestety, nie ma po prostu małej właściwości, którą można ustawić, aby wyrównać granicę na zewnątrz. Rysuje wyrównane do wewnątrz, ponieważ domyślne operacje rysowania UIViews rysują w jego granicach.

Najprostszym rozwiązaniem, jakie przychodzi do głowy, byłoby rozszerzenie UIView o rozmiar szerokości obramowania podczas nakładania obramowania:

CGFloat borderWidth = 2.0f;

self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;
Elliott James Perry
źródło
Użyłem tego w widoku uitableviewcell. ale w wierszu self.frame = CGRectInset (self.frame, -borderWidth, -borderWidth); Szerokość i wysokość widoku rosną, jeśli będziemy przewijać widok tabeli. Skończyło się na usunięciu tego widoku.
Neela
Ta metoda stale zwiększa wysokość widoku, więc usuwam ten kod i używam następującego kodu self.view.layer.borderColor = [[UIColor colorWithRed: 209.0f / 255.0f green: 33.0f / 255.0f blue: 8.0f / 255.0f alpha: 1.0f] CGColor]; self.view.layer.borderWidth = 1.0f;
Rizwan Shah,
4
Wypróbowałem to rozwiązanie w Swift, niestety nie działa, obramowanie wciąż rysuje się wewnątrz UIImageView
theDC
Jeśli ten kod znajduje się wewnątrz metody, która jest wywoływana wiele razy, rozmiar będzie się zwiększał, ponieważ self.frame powtarza się. Aby temu zapobiec, zadeklaruj właściwość do przechowywania self.frame i ustaw ją w viewDidload. na przykład _originalSize = self.frame; gdzie originalSize jest właściwością CGRect. Następnie zmień kod na self.frame = CGRectInset (_originalSize, -borderWidth, -borderWidth);
GeneCode
Gdy jest to zaimplementowane w ViewDidLoad, widok automatycznie zmienia rozmiar do rozmiaru ekranu (tj. Widok zmienia rozmiar do oryginalnego rozmiaru, a tym samym ramka staje się widoczna). Jeśli jest zaimplementowany w ViewDidAppear, w ogóle nie tworzy obramowania poza oryginalnym widokiem. Masz jakieś przemyślenia, jak to zaimplementować, aby obramowanie było tworzone poza oryginalnym widokiem ORAZ widok nie zmieniał rozmiaru?
JeffB6688,
23

Ok, jest już zaakceptowana odpowiedź, ale myślę, że jest lepszy sposób, aby to zrobić, wystarczy mieć nową warstwę nieco większą niż twój widok i nie maskować jej do granic warstwy widoku (która w rzeczywistości jest zachowanie domyślne). Oto przykładowy kod:

CALayer * externalBorder = [CALayer layer];
externalBorder.frame = CGRectMake(-1, -1, myView.frame.size.width+2, myView.frame.size.height+2);
externalBorder.borderColor = [UIColor blackColor].CGColor;
externalBorder.borderWidth = 1.0;

[myView.layer addSublayer:externalBorder];
myView.layer.masksToBounds = NO;

Oczywiście dzieje się tak, jeśli chcesz, aby obramowanie było duże o 1 jednostkę, jeśli chcesz więcej, odpowiednio dostosuj borderWidthramkę warstwy i ramkę. Jest to lepsze niż użycie drugiego widoku nieco większego, ponieważ a CALayerjest lżejsze niż a UIViewi nie musisz modyfikować ramki myView, co jest dobre, na przykład, jeśli myViewjestUIImageView

NB: Dla mnie wynik nie był doskonały na symulatorze (warstwa nie była dokładnie we właściwej pozycji, więc czasami warstwa była grubsza z jednej strony), ale był dokładnie tym, o co proszono na prawdziwym urządzeniu.

EDYTOWAĆ

Właściwie problem o którym mówię w NB był tylko dlatego, że zmniejszyłem ekran symulatora, przy normalnym rozmiarze absolutnie nie ma problemu

Mam nadzieję, że to pomoże

Hugues Duvillier
źródło
2
myView.layer.masksToBounds = NIE; stwarza problem, gdy myView ma CornerRadius.
umakanta
.masksToBounds = no // spowoduje to również przepełnienie obrazu, jeśli jego tryb skalowania to AspectFill
iOS Blacksmith
22

Dzięki powyższej akceptowanej najlepszej odpowiedzi doświadczyłem takich niezbyt ładnych wyników i brzydkich krawędzi:

obramowanie bez ścieżki Beziera

Więc podzielę się z Tobą moim rozszerzeniem UIView Swift , które zamiast tego używa UIBezierPath jako konturu obramowania - bez brzydkich krawędzi (zainspirowane @Fattie ):

granica ze ścieżką Beziera

//  UIView+BezierPathBorder.swift

import UIKit

extension UIView {

    fileprivate var bezierPathIdentifier:String { return "bezierPathBorderLayer" }

    fileprivate var bezierPathBorder:CAShapeLayer? {
        return (self.layer.sublayers?.filter({ (layer) -> Bool in
            return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil
        }) as? [CAShapeLayer])?.first
    }

    func bezierPathBorder(_ color:UIColor = .white, width:CGFloat = 1) {

        var border = self.bezierPathBorder
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask

        if (border == nil) {
            border = CAShapeLayer()
            border!.name = self.bezierPathIdentifier
            self.layer.addSublayer(border!)
        }

        border!.frame = self.bounds
        let pathUsingCorrectInsetIfAny =
            UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius)

        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor
        border!.strokeColor = color.cgColor
        border!.lineWidth = width * 2
    }

    func removeBezierPathBorder() {
        self.layer.mask = nil
        self.bezierPathBorder?.removeFromSuperlayer()
    }

}

Przykład:

let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.layer.cornerRadius = view.frame.width / 2
view.backgroundColor = .red

//add white 2 pixel border outline
view.bezierPathBorder(.white, width: 2)

//remove border outline (optional)
view.removeBezierPathBorder()
Peter Kreinz
źródło
w moim przypadku powinien być w środku :)
Peter Kreinz
dobry sposób myślenia, ale twoja granica jest nadal "w środku", więc trzeba to naprawić, aby była zarysowana :)
Serj Rubens
17

W przypadku implementacji Swift możesz dodać to jako rozszerzenie UIView.

extension UIView {

    struct Constants {
        static let ExternalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.whiteColor()) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRectMake(-borderWidth, -borderWidth, frame.size.width + 2 * borderWidth, frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.CGColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, atIndex: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.ExternalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.ExternalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }

}
picciano
źródło
Świetny! Dziękuję Ci! Niektóre rzeczy zostały zmienione w Swift 4 i teraz wygląda inaczej, ale Xcode wyjaśnia, jak zmodyfikować kod. A w metodzie „removeExternalBorder” musi być „guard externalBorder.name ==”.
DmitryKanunnikoff
13

Cóż, nie ma na to bezpośredniej metody.Możesz rozważyć kilka obejść.

  1. Zmień i zwiększ ramkę oraz dodaj kolor obramowania, tak jak to zrobiłeś
  2. Dodaj widok za bieżącym widokiem o większym rozmiarze, aby pojawił się jako obramowanie. Może pracować jako niestandardowa klasa widoku
  3. Jeśli nie potrzebujesz określonej ramki (wyraźnej granicy), możesz polegać na cieniu do tego celu

    [view1 setBackgroundColor:[UIColor blackColor]];
    UIColor *color = [UIColor yellowColor];
    view1.layer.shadowColor = [color CGColor];
    view1.layer.shadowRadius = 10.0f;
    view1.layer.shadowOpacity = 1;
    view1.layer.shadowOffset = CGSizeZero;
    view1.layer.masksToBounds = NO;
    
Lithu TV
źródło
2

Przed dodaniem obramowania zwiększ szerokość i wysokość ramki widoku o szerokość obramowania:

float borderWidth = 2.0f
CGRect frame = self.frame;
frame.width += borderWidth;
frame.height += borderWidth;
 self.layer.borderColor = [UIColor yellowColor].CGColor;
 self.layer.borderWidth = 2.0f;
Hossam Ghareeb
źródło
2

W rzeczywistości istnieje bardzo proste rozwiązanie. Po prostu ustaw je w ten sposób:

view.layer.borderWidth = 5

view.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor

view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.25).cgColor

źródło
1

Podobało mi się rozwiązanie @picciano Jeśli chcesz eksplodować koło zamiast kwadratu, zamień funkcję addExternalBorder na :

func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.cornerRadius = (frame.size.width + 2 * borderWidth) / 2
        externalBorder.name = Constants.ExternalBorderName
        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

    }
Maksim Kniazev
źródło
0

Podobało mi się rozwiązanie @picciano & @Maksim Kniazev. Możemy również utworzyć obramowanie pierścieniowe z:

func addExternalAnnularBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
    let externalBorder = CALayer()
    externalBorder.frame = CGRect(x: -borderWidth*2, y: -borderWidth*2, width: frame.size.width + 4 * borderWidth, height: frame.size.height + 4 * borderWidth)
    externalBorder.borderColor = borderColor.cgColor
    externalBorder.borderWidth = borderWidth
    externalBorder.cornerRadius = (frame.size.width + 4 * borderWidth) / 2
    externalBorder.name = Constants.ExternalBorderName
    layer.insertSublayer(externalBorder, at: 0)
    layer.masksToBounds = false
}
Jimbung
źródło
0

Jak umieściłem obramowanie wokół mojego widoku interfejsu użytkownika (główny - reklama subskrypcyjna) w Storyboard, to umieszczenie go w innym widoku interfejsu użytkownika (tło - reklama w tle). Tło UIView ma kolor tła, który odpowiada żądanemu kolorowi obramowania, a główny element UIView ma ograniczenia o wartości 2 z każdej strony.

Połączę widok tła z moim ViewController, a następnie włączam i wyłączam obramowanie, zmieniając kolor tła.

Obraz zagnieżdżonego UIView z ograniczeniami widoku z góry 2px dookoła, dzięki czemu jest mniejszy niż większy

JonesJr876
źródło
0

Szybki 5

extension UIView {
    fileprivate struct Constants {
        static let externalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.externalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.externalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }
}
Pacyjent
źródło