Cách tốt nhất để thêm bóng đổ vào UIView của tôi là gì


108

Tôi đang cố gắng thêm bóng đổ vào các chế độ xem được xếp chồng lên nhau, các chế độ xem thu gọn lại cho phép nội dung ở các chế độ xem khác được nhìn thấy, theo cách này, tôi muốn view.clipsToBoundsBẬT để khi các chế độ xem thu gọn, nội dung của chúng sẽ được cắt bớt.

Điều này dường như khiến tôi gặp khó khăn khi thêm bóng đổ vào các lớp vì khi tôi clipsToBoundsBẬT các bóng đổ cũng bị cắt bớt.

Tôi đã cố gắng điều khiển view.frameview.boundsđể thêm bóng đổ vào khung hình nhưng cho phép các giới hạn đủ lớn để bao trùm nó, tuy nhiên tôi đã không gặp may với điều này.

Đây là mã tôi đang sử dụng để thêm Bóng đổ (điều này chỉ hoạt động với clipsToBoundsTẮT như được hiển thị)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Đây là ảnh chụp màn hình của bóng đổ được áp dụng cho lớp màu xám sáng nhất trên cùng. Hy vọng rằng điều này cung cấp ý tưởng về cách nội dung của tôi sẽ trùng lặp nếu clipsToBoundsbị TẮT.

Ứng dụng Shadow.

Làm cách nào để thêm bóng vào UIViewvà giữ cho nội dung của tôi được cắt bớt?

Chỉnh sửa: Tôi chỉ muốn nói thêm rằng tôi cũng đã thử sử dụng hình nền có đổ bóng, cách này hoạt động tốt, tuy nhiên tôi vẫn muốn biết giải pháp mã hóa tốt nhất cho việc này.

Câu trả lời:


280

Thử cái này:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

Trước hết : Được UIBezierPathsử dụng như shadowPathlà rất quan trọng. Nếu không sử dụng, bạn có thể không nhận thấy sự khác biệt lúc đầu, nhưng con mắt tinh tường sẽ quan sát thấy độ trễ nhất định xảy ra trong các sự kiện như xoay thiết bị và / hoặc tương tự. Đó là một tinh chỉnh hiệu suất quan trọng.

Về vấn đề của bạn cụ thể : Dòng quan trọng là view.layer.masksToBounds = NO. Nó vô hiệu hóa việc cắt bớt các lớp con của lớp của chế độ xem mở rộng ra xa hơn giới hạn của chế độ xem.

Đối với những người tự hỏi sự khác biệt giữa masksToBounds(trên lớp) và thuộc tính riêng của chế độ xem clipToBoundslà gì: Thực sự không có bất kỳ. Chuyển đổi một sẽ có ảnh hưởng đến khác. Chỉ là một mức độ trừu tượng khác.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}

1
Cảm ơn, tôi đã thử mã của bạn và sau đó thử thêm masksToBounds = NO;vào bản gốc của tôi - với cả hai lần thử, tôi đều clipsToBounds = YES;BẬT - cả hai đều không thể cắt nội dung. xem bản tóm tắt màn hình về những gì đã xảy ra với ví dụ của bạn> youtu.be/tdpemc_Xdps
Wez

1
Bất kỳ ý tưởng nào về cách làm cho bóng đổ ôm lấy một góc tròn hơn là hiển thị như thể góc vẫn là một hình vuông?
John Erck

7
@JohnErck thử điều này: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Không phải thử nghiệm nhưng sẽ mang lại kết quả bạn muốn.
pkluz

1
Tôi đã học được rằng khi tạo hoạt ảnh cho chế độ xem, bóng đổ sẽ để lại các vệt xám trong khi di chuyển. Loại bỏ các dòng shadowPath giải quyết này
ishahak

1
@shoe bởi vì bất kỳ kích thước thời gian nào của chế độ xem thay đổi, layoutSubviews()đều được gọi. Và nếu chúng tôi không bù đắp ở vị trí đó bằng cách điều chỉnh shadowPathđể phản ánh kích thước hiện tại, chúng tôi sẽ có một chế độ xem được thay đổi kích thước nhưng bóng sẽ vẫn ở kích thước cũ (ban đầu) và không hiển thị hoặc nhìn ra nơi nó không nên .
pkluz

65

Câu trả lời của Wasabii trong Swift 2.3:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

Và trong Swift 3/4/5:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

Đặt mã này trong layoutSubviews () nếu bạn đang sử dụng Tự động thanh toán.

Trong SwiftUI, tất cả điều này dễ dàng hơn nhiều:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)

11
Cảm ơn vì đã lưu ýlayoutSubviews
Islam Q.

1
hoặc thậm chí tốt hơn trong viewDidLayoutSubviews ()
Max MacLeod

1
thích initialisers nhanh chóng struct trên Make biến thể, tức làCGSize(width: 0, height: 0.5)
Oliver Atkinson

Tôi thêm mã này vào layoutSubviews (), nó vẫn không hiển thị trong IB. Có cài đặt nào khác mà tôi phải chọn không?
lừa

Cảm ơn. Tôi đang sử dụng Bố cục Tự động và tôi tự nghĩ - tốt .. view.boundsđã không được tạo rõ ràng đến mức shadowPathkhông thể tham chiếu trực tiếp của chế độ xem mà CGRectZerothay vào đó là một số . Dù sao, đặt điều này dưới sự layoutSubviewsgiải quyết vấn đề của tôi cuối cùng!
devdc,

13

Bí quyết là xác định đúng thuộc masksToBoundstính của lớp trong chế độ xem của bạn:

view.layer.masksToBounds = NO;

và nó sẽ hoạt động.

( Nguồn )


13

Bạn có thể tạo tiện ích mở rộng cho UIView để truy cập các giá trị này trong trình chỉnh sửa thiết kế

Tùy chọn bóng trong trình chỉnh sửa thiết kế

extension UIView{

    @IBInspectable var shadowOffset: CGSize{
        get{
            return self.layer.shadowOffset
        }
        set{
            self.layer.shadowOffset = newValue
        }
    }

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

    @IBInspectable var shadowRadius: CGFloat{
        get{
            return self.layer.shadowRadius
        }
        set{
            self.layer.shadowRadius = newValue
        }
    }

    @IBInspectable var shadowOpacity: Float{
        get{
            return self.layer.shadowOpacity
        }
        set{
            self.layer.shadowOpacity = newValue
        }
    }
}

làm thế nào để tham khảo phần mở rộng trong xây dựng giao diện
Amr Angry

IBInspectable làm cho nó có sẵn trong xây dựng giao diện
Nitesh

Tôi có thể đạt được điều này trong Mục tiêu C không?
Harish Pathak

2
nếu bạn muốn có một bản xem trước thời gian thực (trong xây dựng giao diện), ở đây bạn có thể tìm thấy một bài viết tốt về nó spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill

7

Bạn cũng có thể đặt bóng cho chế độ xem của mình từ bảng phân cảnh

nhập mô tả hình ảnh ở đây


2

Trên viewWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

Sử dụng phần mở rộng của UIView:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

Sử dụng:

sampleView.addDropShadowToView(sampleView)

1
Chỉ cần buộc targetView: UIViewvà loại bỏ tóc mái.
bluebamboo

6
Tại sao không sử dụng self? Có vẻ thuận tiện hơn nhiều đối với tôi.
LinusGeffarth

3
Cách tốt hơn để làm điều này là loại bỏ targetView dưới dạng tham số và sử dụng self thay vì targetView. Điều này loại bỏ dư thừa và cho phép một để gọi nó như vậy: sampleView.addDropShadowToView ()
maxcodes

Nhận xét của Max Nelson rất tuyệt. Đề nghị của tôi là sử dụng if letcú pháp thay vì!
Johnny

0

Vì vậy, có, bạn nên thích thuộc tính shadowPath hơn cho hiệu suất, ngoài ra: Từ tệp tiêu đề của CALayer.shadowPath

Việc chỉ định đường dẫn một cách rõ ràng bằng cách sử dụng thuộc tính này thường sẽ * cải thiện hiệu suất hiển thị, cũng như chia sẻ cùng một đường dẫn * tham chiếu trên nhiều lớp

Một thủ thuật ít được biết đến là chia sẻ cùng một tham chiếu trên nhiều lớp. Tất nhiên chúng phải sử dụng cùng một hình dạng, nhưng điều này thường xảy ra với các ô dạng xem bảng / bộ sưu tập.

Tôi không biết tại sao nó lại nhanh hơn nếu bạn chia sẻ các trường hợp, tôi đoán nó lưu vào bộ nhớ cache kết xuất của bóng đổ và có thể sử dụng lại nó cho các trường hợp khác trong chế độ xem. Tôi tự hỏi liệu điều này có nhanh hơn với

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.