내 UIView에 그림자를 추가하는 가장 좋은 방법은 무엇입니까?


108

다른 뷰의 콘텐츠를 볼 수 view.clipsToBounds있도록 뷰가 축소되고 뷰가 축소되어 뷰가 축소 될 때 해당 콘텐츠가 잘 리도록 유지하고 싶습니다 .

이로 인해 레이어에 그림자를 추가하기가 어려웠던 것 같습니다 clipsToBounds.

나는 조작 view.frame하고 view.bounds프레임에 그림자를 추가하기 위해 노력해 왔지만 경계를 포함 할 수있을만큼 충분히 커지도록 허용했지만 이것에 대해 운이 없었습니다.

다음은 Shadow를 추가하는 데 사용하는 코드입니다 ( clipsToBounds표시된대로 OFF 에서만 작동 함 ).

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

다음은 가장 밝은 회색 레이어에 적용되는 그림자의 스크린 샷입니다. 이것이 clipsToBoundsOFF 인 경우 내 콘텐츠가 어떻게 겹칠 지에 대한 아이디어를 제공하기를 바랍니다 .

섀도우 애플리케이션.

그림자를 추가하고 UIView콘텐츠를 잘린 상태로 유지하려면 어떻게해야합니까?

편집 : 그림자가있는 배경 이미지를 사용하여 잘 작동한다고 덧붙이고 싶었지만 여전히 최선의 코딩 솔루션을 알고 싶습니다.

답변:


280

이 시도:

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;

우선 : 그대로 UIBezierPath사용하는 shadowPath것이 중요합니다. 사용하지 않으면 처음에는 차이를 느끼지 못할 수도 있지만 예리한 눈은 장치 회전 및 / 또는 이와 유사한 이벤트 중에 발생하는 특정 지연을 관찰 할 것입니다. 중요한 성능 조정입니다.

구체적으로 귀하의 문제와 관련하여 : 중요한 라인은 view.layer.masksToBounds = NO입니다. 뷰의 경계보다 더 확장 된 뷰 레이어의 하위 레이어 클리핑을 비활성화합니다 .

masksToBounds(레이어에서) 뷰의 자체 clipToBounds속성 의 차이점이 무엇인지 궁금한 사람들을 위해 : 실제로는 없습니다. 하나를 토글하면 다른 하나에 영향을 미칩니다. 다른 수준의 추상화입니다.


스위프트 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
}

스위프트 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
감사합니다. 귀하의 코드를 시도한 다음 masksToBounds = NO;원본에 추가 하려고 시도했습니다. 두 번의 시도를 모두 유지하면서 모두 clipsToBounds = YES;콘텐츠를 자르지 못했습니다. 여기에 귀하의 예에서 일어난
Wez

1
모서리가 여전히 정사각형 인 것처럼 렌더링하는 대신 그림자가 둥근 모서리를 감싸도록하는 방법에 대한 아이디어가 있습니까?
John Erck 2014 년

7
@JohnErck이 시도해보십시오 : UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. 테스트되지 않았지만 원하는 결과를 얻을 수 있습니다.
pkluz 2014 년

1
뷰를 애니메이션 할 때 그림자가 이동하는 동안 회색 트랙을 남긴다는 것을 배웠습니다. shadowPath 라인을 제거하면이 해결
ishahak

1
뷰의 크기가 변경 될 때마다 @shoe layoutSubviews()가 호출됩니다. 그리고 shadowPath현재 크기를 반영하도록 을 조정하여 해당 위치를 보정하지 않으면 크기가 조정되었지만 그림자는 여전히 이전 (초기) 크기로 표시되지 않거나 표시되지 않아야하는 부분을 엿볼 수 있습니다. .
pkluz

65

Swift 2.3에서 Wasabii의 답변 :

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

그리고 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

AutoLayout을 사용하는 경우이 코드를 layoutSubviews ()에 넣으십시오.

SwiftUI에서는이 모든 것이 훨씬 쉽습니다.

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

11
주목 해 주셔서 감사합니다layoutSubviews
Islam Q.

1
또는 더 나은 viewDidLayoutSubviews ()에서
최대 MacLeod는

1
Make 변형보다 신속한 구조체 이니셜 라이저를 선호합니다. 즉CGSize(width: 0, height: 0.5)
Oliver Atkinson

이 코드를 layoutSubviews ()에 추가했지만 여전히 IB에서 렌더링되지 않았습니다. 옵트 인해야하는 다른 설정이 있습니까?
donkey

감사. 나는 자동 레이아웃을 사용하고 있었고 내 자신에 대해 생각했습니다. .. 보기의 사각형을 참조 할 수 없지만 대신 일부 를 참조 할 수 없을 view.bounds정도로 명확하게 생성되지 않았습니다 . 어쨌든, 이것을 밑에두면 마침내 내 문제가 해결되었습니다! shadowPathCGRectZerolayoutSubviews
devdc

13

트릭은 masksToBounds뷰 레이어 의 속성을 올바르게 정의하는 것입니다.

view.layer.masksToBounds = NO;

그리고 그것은 작동합니다.

( 출처 )


13

디자인 편집기에서 이러한 값에 액세스하기 위해 UIView에 대한 확장을 만들 수 있습니다.

디자인 편집기의 그림자 옵션

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
        }
    }
}

어떻게 인터페이스 빌더에서 그 확장을 참조하십시오
아 므르 화가

IBInspectable 인터페이스 빌더에서 사용할 수 있도록
Nitesh

Objective C에서도 이것을 달성 할 수 있습니까?
Harish Pathak를

2
실시간 미리보기 (인터페이스 빌더에서)를 원하신다면 여기 spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill

7

스토리 보드에서보기에 그림자를 설정할 수도 있습니다.

여기에 이미지 설명 입력


2

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
}

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
    }
}

용법:

sampleView.addDropShadowToView(sampleView)

1
targetView: UIView앞머리를 강요 하고 제거하십시오.
bluebamboo

6
왜 사용하지 self않습니까? 나에게 훨씬 더 편리해 보인다.
LinusGeffarth

3
이를 수행하는 더 좋은 방법은 targetView를 매개 변수로 제거하고 targetView 대신 self를 사용하는 것입니다. 이렇게하면 중복이 제거되고 다음과 같이 호출 할 수 있습니다. sampleView.addDropShadowToView ()
maxcodes

Max Nelson의 의견은 훌륭합니다. 나의 제안은 사용입니다 if let구문 대신에!
조니

0

그렇습니다. 성능을 위해 shadowPath 속성을 선호해야합니다. 또한 : CALayer.shadowPath의 헤더 파일에서

이 속성을 사용하여 경로를 명시 적으로 지정하면 일반적으로 * 여러 레이어에서 동일한 경로 * 참조를 공유하므로 렌더링 성능이 향상됩니다.

덜 알려진 트릭은 여러 레이어에서 동일한 참조를 공유하는 것입니다. 물론 동일한 모양을 사용해야하지만 이는 테이블 / 컬렉션 뷰 셀에서 일반적입니다.

인스턴스를 공유하면 왜 더 빨라지는지 모르겠습니다. 그림자 렌더링을 캐시하고 뷰의 다른 인스턴스에 재사용 할 수 있다고 생각합니다. 이게 더 빠른지 궁금합니다

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.