Thêm đường viền bên ngoài UIView (thay vì bên trong)


82

Nếu một thêm đường viền của một chế độ xem bằng cách sử dụng mã trong một chế độ xem như

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

đường viền được thêm vào bên trong dạng xem như sau: nhập mô tả hình ảnh ở đây

chế độ xem bên phải là chế độ xem ban đầu, như bạn có thể thấy, vùng màu đen của chế độ xem được viền nhỏ hơn vùng ban đầu. nhưng những gì tôi muốn nhận được là một bên ngoài biên giới của cái nhìn ban đầu, như thế này: nhập mô tả hình ảnh ở đây. vùng màu đen bằng với vùng ban đầu, làm thế nào tôi có thể thực hiện nó?

Câu trả lời:


100

Thật không may, không chỉ có một thuộc tính nhỏ mà bạn có thể đặt để căn chỉnh đường viền ra bên ngoài. Nó vẽ được căn chỉnh vào bên trong vì các thao tác vẽ mặc định của UIViews vẽ trong giới hạn của nó.

Giải pháp đơn giản nhất mà bạn nghĩ đến sẽ là mở rộng UIView bằng kích thước của chiều rộng đường viền khi áp dụng đường viền:

CGFloat borderWidth = 2.0f;

self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;

Tôi đã sử dụng điều này trên quan điểm của uitableviewcell. nhưng trên dòng, self.frame = CGRectInset (self.frame, -borderWidth, -borderWidth); Chiều rộng và chiều cao của chế độ xem tiếp tục tăng nếu chúng ta tiếp tục cuộn chế độ xem bảng .. Vì vậy, tôi đã kết thúc bằng cách loại bỏ nó.
Neela

Phương pháp này tiếp tục tăng chiều cao chế độ xem nên tôi xóa mã này và sử dụng mã sau 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
Đã thử giải pháp này trong Swift, không may là không hoạt động, đường viền tiếp tục vẽ bên trong UIImageView
theDC

Nếu mã này nằm trong một phương thức được gọi nhiều lần, thì nó sẽ tiếp tục tăng kích thước vì self.frame tự nhắc lại chính nó. Để tránh điều này, hãy khai báo một thuộc tính để lưu trữ self.frame và đặt nó trong viewDidload. ví dụ: _originalSize = self.frame; trong đó originalSize là một thuộc tính CGRect. Sau đó, thay đổi mã thành self.frame = CGRectInset (_originalSize, -borderWidth, -borderWidth);
GeneCode,

Khi điều này được triển khai trong ViewDidLoad, chế độ xem tự động thay đổi kích thước thành kích thước màn hình (nghĩa là chế độ xem thay đổi kích thước thành kích thước ban đầu và do đó đường viền sẽ hiển thị). Nếu nó được triển khai trong ViewDidAppear, nó hoàn toàn không tạo đường viền bên ngoài chế độ xem ban đầu. Bất kỳ suy nghĩ nào về cách triển khai nó để đường viền được tạo bên ngoài chế độ xem ban đầu VÀ chế độ xem không thay đổi kích thước?
JeffB6688

23

Ok, đã có một câu trả lời được chấp nhận nhưng tôi nghĩ có một cách tốt hơn để làm điều đó, bạn chỉ cần có một lớp mới lớn hơn một chút so với chế độ xem của bạn và không che nó vào giới hạn của lớp của chế độ xem (thực tế là hành vi mặc định). Đây là mã mẫu:

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;

Tất nhiên điều này là nếu bạn muốn đường viền của mình là 1 thống nhất lớn, nếu bạn muốn nhiều hơn, bạn điều chỉnh khung borderWidthvà khung của lớp cho phù hợp. Điều này tốt hơn là sử dụng chế độ xem thứ hai lớn hơn một chút vì a CALayernhẹ hơn a UIViewvà bạn không phải sửa đổi khung của myView, điều này rất tốt, chẳng hạn nếu myViewUIImageView

NB: Đối với tôi, kết quả không hoàn hảo trên trình mô phỏng (lớp không chính xác ở đúng vị trí nên đôi khi lớp dày hơn ở một bên) nhưng chính xác là những gì được yêu cầu trên thiết bị thực.

BIÊN TẬP

Thực ra vấn đề mà tôi nói đến trong NB chỉ là do tôi đã giảm màn hình của trình mô phỏng, ở kích thước bình thường thì hoàn toàn không có vấn đề gì

Hy vọng nó giúp


2
myView.layer.masksToBounds = KHÔNG; nó đang tạo ra vấn đề khi myView có CornerRadius.
umakanta

.masksToBounds = no // điều này cũng sẽ làm tràn hình ảnh của bạn nếu đó là chế độ chia tỷ lệ là angleFill
iOS Blacksmith

22

Với câu trả lời hay nhất được chấp nhận ở trên, tôi đã có những trải nghiệm với kết quả không tốt đẹp và các góc cạnh khó coi:

đường viền không có đường viền

Vì vậy, tôi sẽ chia sẻ tiện ích mở rộng UIView Swift của tôi với bạn, sử dụng UIBezierPath thay vì làm đường viền - không có các cạnh khó coi (lấy cảm hứng từ @Fattie ):

biên giới với đường bezier

//  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()
    }

}

Thí dụ:

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()

trong trường hợp của tôi nó nên bên :)
Peter Kreinz

cách suy nghĩ tốt nhưng biên giới của bạn vẫn ở "bên trong" vì vậy điều này cần được sửa chữa để được phác thảo :)
Serj Rubens

15

Để triển khai Swift, bạn có thể thêm nó dưới dạng tiện ích mở rộng 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()
    }

}

Thông minh! Cảm ơn bạn! Một số thứ đã được thay đổi trong Swift 4 và bây giờ trông khác, nhưng Xcode giải thích cách sửa đổi mã. Và trong phương thức 'removeExternalBorder' phải là 'Guard externalBorder.name =='.
DmitryKanunnikoff

13

Vâng, không có phương pháp trực tiếp nào để làm điều đó. Bạn có thể xem xét một số cách giải quyết.

  1. Thay đổi và tăng khung và thêm màu viền như bạn đã làm
  2. Thêm một dạng xem phía sau dạng xem hiện tại với kích thước lớn hơn để nó xuất hiện dưới dạng đường viền. Có thể được làm việc như một lớp tùy chỉnh của dạng xem
  3. Nếu bạn không cần một đường viền xác định (đường viền xóa) thì bạn có thể phụ thuộc vào bóng cho mục đích

    [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;
    

2

Tăng chiều rộng và chiều cao của khung của chế độ xem với chiều rộng đường viền trước khi thêm đường viền:

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;

2

Thực ra có một giải pháp rất đơn giản. Chỉ cần đặt cả hai như thế này:

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

1

Tôi thích giải pháp của @picciano Nếu bạn muốn hình tròn bùng nổ thay vì hình vuông, hãy thay thế hàm addExternalBorder bằng:

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

    }

0

Tôi thích giải pháp của @picciano & @Maksim Kniazev. Chúng tôi cũng có thể tạo đường viền hình khuyên với sau:

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
}

0

Cách tôi đặt đường viền xung quanh chế độ xem giao diện người dùng của mình (chính - SubscriptionAd) trong Bảng phân cảnh là đặt nó bên trong một chế độ xem giao diện người dùng khác (nền - BackgroundAd). UIView nền có màu nền phù hợp với màu đường viền mà tôi muốn và UIView chính có giá trị ràng buộc 2 từ mỗi bên.

Tôi sẽ liên kết chế độ xem nền với ViewController của mình và sau đó bật và tắt đường viền bằng cách thay đổi màu nền.

Image Of Nested UIView với các ràng buộc 2px ở Chế độ xem trên cùng, làm cho nó nhỏ hơn lớn hơn


0

Swift 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()
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.