UIView 레이어의 내부 그림자 효과?


92

다음 CALayer가 있습니다.

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithObjects:(id)[RGB(130, 0, 140) CGColor], (id)[RGB(108, 0, 120) CGColor], nil];
[self.layer insertSublayer:gradient atIndex:0];

내부 그림자 효과 를 추가하고 싶지만 어떻게해야할지 잘 모르겠습니다. drawRect에서 그릴 필요가 있다고 생각하지만 일부 버튼 뒤에 막대가 있어야하므로 다른 UIView 객체 위에 레이어를 추가하므로 어떻게 해야할지 모르겠습니다.

다른 레이어를 추가 할 수 있지만 내부 그림자 효과를 얻는 방법을 다시 알 수 없습니다 (예 :

여기에 이미지 설명 입력

감사합니다 ...

답변:


108

Costique 제안에 따라 Core Graphics를 사용하여 내부 그림자를 그리는 방법을 궁금해하는 사람은 다음과 같습니다. (iOS에서 필요에 따라 조정)

drawRect : 메서드에서 ...

CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 0.5f * CGRectGetHeight(bounds);


// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);

// Fill this path
UIColor *aColor = [UIColor redColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextFillPath(context);


// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);

// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath); 
CGContextClip(context);         


// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 3.0f, [aColor CGColor]);   

// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];   
CGContextSaveGState(context);   
CGContextAddPath(context, path);
CGContextEOFillPath(context);

// Release the paths
CGPathRelease(path);    
CGPathRelease(visiblePath);

따라서 기본적으로 다음 단계가 있습니다.

  1. 경로 만들기
  2. 원하는 채우기 색상을 설정하고이 경로를 컨텍스트에 추가하고 컨텍스트를 채 웁니다.
  3. 이제 보이는 경로를 묶을 수있는 더 큰 직사각형을 만듭니다. 이 경로를 닫기 전에 보이는 경로를 추가하십시오. 그런 다음 경로를 닫아서 보이는 경로를 뺀 모양을 만듭니다. 이러한 경로를 만든 방법에 따라 채우기 방법 (짝수 / 홀수가 아닌 0이 아닌 감기)을 조사 할 수 있습니다. 본질적으로 하위 경로를 함께 더할 때 "빼기"를 얻으려면 시계 방향과 시계 반대 방향의 반대 방향으로 하위 경로를 그리거나 구성해야합니다.
  4. 그런 다음 보이는 경로를 컨텍스트의 클리핑 경로로 설정해야 화면 외부에 아무것도 그리지 않습니다.
  5. 그런 다음 오프셋, 흐림 및 색상을 포함하는 컨텍스트에 그림자를 설정합니다.
  6. 그런 다음 큰 모양에 구멍을 채우십시오. 색상은 중요하지 않습니다. 모든 것을 올바르게 수행했다면이 색상이 아닌 그림자 만 볼 수 있기 때문입니다.

감사합니다. 반경을 조정할 수 있습니까? 현재는 경계를 기반으로하지만 대신 설정된 반경 (예 : 5.0f)을 기반으로하고 싶습니다. 위의 코드를 사용하면 너무 반올림됩니다.
runmad

2
@runmad 글쎄, 당신은 원하는 어떤 종류의 가시적 인 CGPath를 만들 수 있습니다. 여기에 사용 된 예는 간결함을 위해 선택된 예입니다. 둥근 사각형을 만들려면 다음과 같이하면됩니다. CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect : rect cornerRadius : radius] .CGPath 도움이 되길 바랍니다.
Daniel Thorpe

4
@DanielThorpe : 좋은 답변에 +1. 둥근 사각형 경로 코드 (반경을 변경할 때 끊어짐)를 수정하고 외부 사각형 경로 코드를 단순화했습니다. 괜찮 으시길 바랍니다.
Regexident

2 개가 아닌 4 개 방향에서 내부 그림자를 올바르게 설정하려면 어떻게해야합니까?
Protocole

@Protocole 오프셋을 {0,0}으로 설정할 수 있지만 그림자 반경은 4.f를 사용합니다.
Daniel Thorpe

47

이 파티에 늦었다는 건 알지만, 여행을 일찍하는데 도움이되었을 것입니다 ...

신용이 필요한 곳에 신용을 부여하기 위해 이것은 본질적으로 더 큰 지역에서 더 작은 지역을 빼는 Costique의 솔루션에 대한 Daniel Thorpe의 정교화를 수정 한 것입니다. 이 버전은 재정의하는 대신 레이어 구성을 사용하는 사용자를위한 것입니다.-drawRect:

CAShapeLayer클래스는 동일한 효과를 달성 할 수 있습니다 :

CAShapeLayer* shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:[self bounds]];

// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:5];

// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];

// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);

[shadowLayer setPath:path];
CGPathRelease(path);

[[self layer] addSublayer:shadowLayer];

이 시점에서 부모 레이어가 경계까지 마스킹하지 않으면 레이어 가장자리 주변에 마스크 레이어의 추가 영역이 표시됩니다. 예제를 직접 복사 한 경우 42 픽셀의 검은 색이됩니다. 이를 제거하려면 CAShapeLayer동일한 경로를 가진 다른 것을 사용 하고 그림자 레이어의 마스크로 설정하면됩니다.

CAShapeLayer* maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];

나는 이것을 직접 벤치마킹하지 않았지만 래스터 화와 함께이 접근 방식을 사용하는 것이을 재정의하는 것보다 더 성능이 좋다고 생각 -drawRect:합니다.


3
someInnerPath? 좀 더 설명해 주시겠습니까?
Moe

4
@Moe 원하는 임의의 CGPath 일 수 있습니다. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]가장 간단한 선택입니다.
Matt Wilding

Matt에 대한 건배 :-)
Moe

내부 그림자를 올바르게 그리는 shadowLayer.path에 대해 검은 색 (외부) 사각형이 표시됩니다. 어떻게 제거 할 수 있습니까 (검은 색 바깥 쪽 직사각형)? 컨텍스트 내에서만 fillColor를 설정할 수 있으며 사용하지 않는 것 같습니다.
Olivier

11
이것은 아주 잘 작동합니다! 몇 가지 추가 사항과 함께 github에 업로드했습니다. 시도해보세요 :) github.com/inamiy/YIInnerShadowView
inamiy

35

경계 외부에 큰 직사각형 경로를 만들고 경계 크기의 직사각형 경로를 빼고 결과 경로를 "일반"그림자로 채움으로써 Core Graphics로 내부 그림자를 그릴 수 있습니다.

그러나 그래디언트 레이어와 결합해야하므로 더 쉬운 해결책은 내부 그림자의 9 부분 투명 PNG 이미지를 만들어 올바른 크기로 늘리는 것입니다. 9 부분으로 구성된 그림자 이미지는 다음과 같습니다 (크기는 21x21 픽셀).

대체 텍스트

CALayer *innerShadowLayer = [CALayer layer];
innerShadowLayer.contents = (id)[UIImage imageNamed: @"innershadow.png"].CGImage;
innerShadowLayer.contentsCenter = CGRectMake(10.0f/21.0f, 10.0f/21.0f, 1.0f/21.0f, 1.0f/21.0f);

그런 다음 innerShadowLayer의 프레임을 설정하면 그림자가 제대로 늘어납니다.


네, 당신 말이 맞을 것 같아요. 레이어를 가능한 한 평평하게 만들고 싶었습니다. 내부 그림자와 그라디언트 모양으로 Photoshop에서 이미지를 만들 수 있지만 이미지를 사용할 때 장치에서 색상이 100 % 일치하는 문제가 있습니다.
runmad dec

네, 그것은 모든 그라디언트와 그림자의 문제입니다. iOS에서 이러한 Photoshop 효과를 1 : 1로 재현 할 수는 없습니다.
Costique 2010

29

Swift에서 CALayer 만 사용하는 단순화 된 버전 :

import UIKit

final class FrameView : UIView {
    init() {
        super.init(frame: CGRect.zero)
        backgroundColor = UIColor.white
    }

    @available(*, unavailable)
    required init?(coder decoder: NSCoder) { fatalError("unavailable") }

    override func layoutSubviews() {
        super.layoutSubviews()
        addInnerShadow()
    }

    private func addInnerShadow() {
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        // Shadow path (1pt ring around bounds)
        let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
        let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
        path.append(cutout)
        innerShadow.shadowPath = path.cgPath
        innerShadow.masksToBounds = true
        // Shadow properties
        innerShadow.shadowColor = UIColor(white: 0, alpha: 1).cgColor // UIColor(red: 0.71, green: 0.77, blue: 0.81, alpha: 1.0).cgColor
        innerShadow.shadowOffset = CGSize.zero
        innerShadow.shadowOpacity = 1
        innerShadow.shadowRadius = 3
        // Add
        layer.addSublayer(innerShadow)
    }
}

innerShadow 레이어는 그림자 앞에 렌더링되므로 불투명 한 배경색이 없어야합니다.


마지막 줄에는 '레이어'가 있습니다. 이것은 어디에서 왔습니까?
Charlie Seligman 2015 년

@CharlieSeligman 모든 레이어가 될 수있는 부모 레이어입니다. 사용자 정의 레이어 또는 뷰의 레이어를 사용할 수 있습니다 (UIView에는 레이어 속성이 있음).
패트릭 Pijnappel

이어야합니다 let innerShadow = CALayer(); innerShadow.frame = bounds. 적절한 경계가 없으면 적절한 그림자를 그릴 수 없습니다. 어쨌든 감사합니다
haik.ampardjian

당신은 아마 설정할 불구하고 @noir_eagle 사실에 있다고는 layoutSubviews()동기화를 유지
패트릭 Pijnappel에게

권리! 에서 하나 layoutSubviews()또는draw(_ rect)
haik.ampardjian

24

약간의 순환 방식이지만 이미지를 사용할 필요가 없으며 (읽기 : 색상 변경, 그림자 반경 등) 코드 몇 줄이면 충분합니다.

  1. 드롭 섀도우를 적용 ​​할 UIView의 첫 번째 하위보기로 UIImageView를 추가합니다. IB를 사용하지만 프로그래밍 방식으로 동일한 작업을 수행 할 수 있습니다.

  2. UIImageView에 대한 참조가 'innerShadow'라고 가정합니다.

`

[[innerShadow layer] setMasksToBounds:YES];
[[innerShadow layer] setCornerRadius:12.0f];        
[[innerShadow layer] setBorderColor:[UIColorFromRGB(180, 180, 180) CGColor]];
[[innerShadow layer] setBorderWidth:1.0f];
[[innerShadow layer] setShadowColor:[UIColorFromRGB(0, 0, 0) CGColor]];
[[innerShadow layer] setShadowOffset:CGSizeMake(0, 0)];
[[innerShadow layer] setShadowOpacity:1];
[[innerShadow layer] setShadowRadius:2.0];

주의 사항 : 테두리가 있어야합니다. 그렇지 않으면 그림자가 표시되지 않습니다. [UIColor clearColor]가 작동하지 않습니다. 이 예에서는 다른 색을 사용하지만 그림자의 시작과 같은 색을 갖도록 엉망으로 만들 수 있습니다. :)

UIColorFromRGB매크로 에 대한 아래 bbrame의 설명을 참조하십시오 .


생략했지만 imageview를 추가하는 과정에서이 작업을 수행한다고 가정합니다. 프레임을 부모 UIView와 동일한 사각형으로 설정해야합니다. IB를 사용하는 경우 상위 뷰의 프레임을 변경할 경우 뷰와 함께 그림자 크기를 갖도록 스트럿과 스프링을 오른쪽으로 설정합니다. 코드에는 AFAIK와 같은 작업을 수행 할 수있는 크기 조정 마스크가 있어야합니다.
jinglesthula

지금은 이것이 가장 쉬운 방법이지만 CALayer 섀도우 메서드는 iOS 3.2 이상에서만 사용할 수 있습니다. 3.1을 지원하므로 if ([layer respondsToSelector : @selector (setShadowColor :)]) {
DougW

이것은 나를 위해 작동하지 않는 것 같습니다. 적어도 xcode 4.2 및 ios 시뮬레이터 4.3에서. 그림자를 보이게하려면 배경색을 추가해야합니다. 그 지점에서 그림자가 외부에만 나타납니다.
Andrea

@Andrea-위에서 언급 한주의 사항을 명심하십시오. 배경색이나 테두리는 '그림자를 추가 할 무언가를주는 것'과 같은 효과를 가질 수 있다고 생각합니다. 외부에 나타나는 경우 UIImageView가 내부 그림자를 원하는 하위 뷰가 아닌 경우 코드를 확인해야합니다.
jinglesthula

이전 진술을 수정하기 위해 ... 코드가 실제로 작동합니다 ... 뭔가 빠졌지 만 안타깝게도 지금은 기억할 수 없습니다. :) 그래서 ...이 코드를 공유 해주셔서 감사합니다.
Andrea

17

안하는 것보다 늦게하는 것이 낫다...

여기에 이미 게시 된 것보다 낫지는 않은 또 다른 접근 방식이 있지만 멋지고 간단합니다.

-(void)drawInnerShadowOnView:(UIView *)view
{
    UIImageView *innerShadowView = [[UIImageView alloc] initWithFrame:view.bounds];

    innerShadowView.contentMode = UIViewContentModeScaleToFill;
    innerShadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [view addSubview:innerShadowView];

    [innerShadowView.layer setMasksToBounds:YES];

    [innerShadowView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
    [innerShadowView.layer setShadowColor:[UIColor blackColor].CGColor];
    [innerShadowView.layer setBorderWidth:1.0f];

    [innerShadowView.layer setShadowOffset:CGSizeMake(0, 0)];
    [innerShadowView.layer setShadowOpacity:1.0];

    // this is the inner shadow thickness
    [innerShadowView.layer setShadowRadius:1.5];
}

@SomaMan은 특정면으로 만 그림자를 설정할 수 있습니까? 단지 상단 또는 위 / 아래 또는 상단 / 우측 등처럼
Mitesh Dobareeya

8

drawRect로 내부 그림자를 그리거나 UIView를 View에 추가하는 대신. 예를 들어 UIView V의 바닥에 내부 그림자 효과를 원하는 경우 CALayer를 테두리에 직접 추가 할 수 있습니다.

innerShadowOwnerLayer = [[CALayer alloc]init];
innerShadowOwnerLayer.frame = CGRectMake(0, V.frame.size.height+2, V.frame.size.width, 2);
innerShadowOwnerLayer.backgroundColor = [UIColor whiteColor].CGColor;

innerShadowOwnerLayer.shadowColor = [UIColor blackColor].CGColor;
innerShadowOwnerLayer.shadowOffset = CGSizeMake(0, 0);
innerShadowOwnerLayer.shadowRadius = 10.0;
innerShadowOwnerLayer.shadowOpacity = 0.7;

[V.layer addSubLayer:innerShadowOwnerLayer];

이것은 대상 UIView에 대한 하단 내부 그림자를 추가합니다.


6

여기서 빠른 변화의 버전이다 startPointendPoint각 측면에 있도록.

        let layer = CAGradientLayer()
        layer.startPoint    = CGPointMake(0.5, 0.0);
        layer.endPoint      = CGPointMake(0.5, 1.0);
        layer.colors        = [UIColor(white: 0.1, alpha: 1.0).CGColor, UIColor(white: 0.1, alpha: 0.5).CGColor, UIColor.clearColor().CGColor]
        layer.locations     = [0.05, 0.2, 1.0 ]
        layer.frame         = CGRectMake(0, 0, self.view.frame.width, 60)
        self.view.layer.insertSublayer(layer, atIndex: 0)

나를 위해 일했습니다!! 감사합니다.
iUser

5

이것은 PaintCode 에서 내 보낸 솔루션입니다 .

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Shadow Declarations
    UIColor* shadow = UIColor.whiteColor;
    CGSize shadowOffset = CGSizeMake(0, 0);
    CGFloat shadowBlurRadius = 10;

    //// Rectangle Drawing
    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.bounds];
    [[UIColor blackColor] setFill];
    [rectanglePath fill];

    ////// Rectangle Inner Shadow
    CGContextSaveGState(context);
    UIRectClip(rectanglePath.bounds);
    CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);

    CGContextSetAlpha(context, CGColorGetAlpha([shadow CGColor]));
    CGContextBeginTransparencyLayer(context, NULL);
    {
        UIColor* opaqueShadow = [shadow colorWithAlphaComponent: 1];
        CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [opaqueShadow CGColor]);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextBeginTransparencyLayer(context, NULL);

        [opaqueShadow setFill];
        [rectanglePath fill];

        CGContextEndTransparencyLayer(context);
    }
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
}

3

나는 파티에 매우 늦었지만 커뮤니티에 돌려주고 싶습니다. 이것은 내가 정적 라이브러리와 리소스를 제공하지 않았기 때문에 UITextField 배경 이미지를 제거하기 위해 작성한 방법입니다. One character raw 또는 (BOOL) [self isUsingBullets] 또는 (BOOL) [self usingAsterisks]를 ViewController에 표시 할 수있는 4 개의 UITextField 인스턴스의 PIN 입력 화면. 앱은 iPhone / iPhone retina / iPad / iPad Retina 용이므로 4 개의 이미지를 제공 할 필요가 없습니다.

#import <QuartzCore/QuartzCore.h>

- (void)setTextFieldInnerGradient:(UITextField *)textField
{

    [textField setSecureTextEntry:self.isUsingBullets];
    [textField setBackgroundColor:[UIColor blackColor]];
    [textField setTextColor:[UIColor blackColor]];
    [textField setBorderStyle:UITextBorderStyleNone];
    [textField setClipsToBounds:YES];

    [textField.layer setBorderColor:[[UIColor blackColor] CGColor]];
    [textField.layer setBorderWidth:1.0f];

    // make a gradient off-white background
    CAGradientLayer *gradient = [CAGradientLayer layer];
    CGRect gradRect = CGRectInset([textField bounds], 3, 3);    // Reduce Width and Height and center layer
    gradRect.size.height += 2;  // minimise Bottom shadow, rely on clipping to remove these 2 pts.

    gradient.frame = gradRect;
    struct CGColor *topColor = [UIColor colorWithWhite:0.6f alpha:1.0f].CGColor;
    struct CGColor *bottomColor = [UIColor colorWithWhite:0.9f alpha:1.0f].CGColor;
    // We need to use this fancy __bridge object in order to get the array we want.
    gradient.colors = [NSArray arrayWithObjects:(__bridge id)topColor, (__bridge id)bottomColor, nil];
    [gradient setCornerRadius:4.0f];
    [gradient setShadowOffset:CGSizeMake(0, 0)];
    [gradient setShadowColor:[[UIColor whiteColor] CGColor]];
    [gradient setShadowOpacity:1.0f];
    [gradient setShadowRadius:3.0f];

    // Now we need to Blur the edges of this layer "so it blends"
    // This rasterizes the view down to 4x4 pixel chunks then scales it back up using bilinear filtering...
    // it's EXTREMELY fast and looks ok if you are just wanting to blur a background view under a modal view.
    // To undo it, just set the rasterization scale back to 1.0 or turn off rasterization.
    [gradient setRasterizationScale:0.25];
    [gradient setShouldRasterize:YES];

    [textField.layer insertSublayer:gradient atIndex:0];

    if (self.usingAsterisks) {
        [textField setFont:[UIFont systemFontOfSize:80.0]];
    } else {
        [textField setFont:[UIFont systemFontOfSize:40.0]];
    }
    [textField setTextAlignment:UITextAlignmentCenter];
    [textField setEnabled:NO];
}

이 포럼이 도움이 되었기 때문에 누군가에게 도움이 되었기를 바랍니다.


3

의 위대한 기사 확인 석영의 내부 그림자 에 의해 크리스 에머리 느릅 나무 설명 내부 그림자에 의해 그려진하는 방법 PaintCode 하고 깨끗하고 깔끔한 코드를 제공을 :

- (void)drawInnerShadowInContext:(CGContextRef)context
                        withPath:(CGPathRef)path
                     shadowColor:(CGColorRef)shadowColor
                          offset:(CGSize)offset
                      blurRadius:(CGFloat)blurRadius 
{
    CGContextSaveGState(context);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);

    CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
    CGContextBeginTransparencyLayer(context, NULL);
        CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextSetFillColorWithColor(context, opaqueShadowColor);
        CGContextAddPath(context, path);
        CGContextFillPath(context);
    CGContextEndTransparencyLayer(context);

    CGContextRestoreGState(context);

    CGColorRelease(opaqueShadowColor);
}

3

다음은 Swift 4.2의 솔루션입니다. 시도 하시겠습니까?

final class ACInnerShadowLayer : CAShapeLayer {

  var innerShadowColor: CGColor? = UIColor.black.cgColor {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOffset: CGSize = .zero {
    didSet { setNeedsDisplay() }
  }

  var innerShadowRadius: CGFloat = 8 {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOpacity: Float = 1 {
    didSet { setNeedsDisplay() }
  }

  override init() {
    super.init()

    masksToBounds = true
    contentsScale = UIScreen.main.scale

    setNeedsDisplay()
  }

  override init(layer: Any) {
      if let layer = layer as? InnerShadowLayer {
          innerShadowColor = layer.innerShadowColor
          innerShadowOffset = layer.innerShadowOffset
          innerShadowRadius = layer.innerShadowRadius
          innerShadowOpacity = layer.innerShadowOpacity
      }
      super.init(layer: layer)
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func draw(in ctx: CGContext) {
    ctx.setAllowsAntialiasing(true)
    ctx.setShouldAntialias(true)
    ctx.interpolationQuality = .high

    let colorspace = CGColorSpaceCreateDeviceRGB()

    var rect = bounds
    var radius = cornerRadius

    if borderWidth != 0 {
      rect = rect.insetBy(dx: borderWidth, dy: borderWidth)
      radius -= borderWidth
      radius = max(radius, 0)
    }

    let innerShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
    ctx.addPath(innerShadowPath)
    ctx.clip()

    let shadowPath = CGMutablePath()
    let shadowRect = rect.insetBy(dx: -rect.size.width, dy: -rect.size.width)
    shadowPath.addRect(shadowRect)
    shadowPath.addPath(innerShadowPath)
    shadowPath.closeSubpath()

    if let innerShadowColor = innerShadowColor, let oldComponents = innerShadowColor.components {
      var newComponets = Array<CGFloat>(repeating: 0, count: 4) // [0, 0, 0, 0] as [CGFloat]
      let numberOfComponents = innerShadowColor.numberOfComponents

      switch numberOfComponents {
      case 2:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[0]
        newComponets[2] = oldComponents[0]
        newComponets[3] = oldComponents[1] * CGFloat(innerShadowOpacity)
      case 4:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[1]
        newComponets[2] = oldComponents[2]
        newComponets[3] = oldComponents[3] * CGFloat(innerShadowOpacity)
      default:
        break
      }

      if let innerShadowColorWithMultipliedAlpha = CGColor(colorSpace: colorspace, components: newComponets) {
        ctx.setFillColor(innerShadowColorWithMultipliedAlpha)
        ctx.setShadow(offset: innerShadowOffset, blur: innerShadowRadius, color: innerShadowColorWithMultipliedAlpha)
        ctx.addPath(shadowPath)
        ctx.fillPath(using: .evenOdd)
      }
    } 
  }
}

별도의 클래스로 사용하지 않고 코드에서 사용하는 것처럼 컨텍스트 (ctx)는 다음과 같은 경우에 nil입니다.let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed UIGraphicsGetCurrentContext 일부 뷰가 컨텍스트를 스택에 푸시 할 때 가져 오는 메서드 로 현재 컨텍스트 를 가져올 수 있습니다 .
Arco

@Arco 장치를 회전 할 때 문제가 발생했습니다. 나는 'override comfort init (layer : Any) {self.init ()}'를 추가했습니다. 이제 오류가 표시되지 않습니다!
Yuma Technical Inc.

충돌을 수정하기 위해 init (layer : Any)를 추가했습니다.
Nik Kov 19.04.23

2

Swift에서 CALayer를 사용하는 확장 가능한 솔루션

설명 InnerShadowLayer을 사용하면 다른 가장자리를 제외한 특정 가장자리에 대해서만 내부 그림자를 활성화 할 수도 있습니다. (예 : 뷰의 왼쪽 및 상단 가장자리에서만 내부 그림자를 활성화 할 수 있음)

그런 다음 다음을 InnerShadowLayer사용하여보기에 를 추가 할 수 있습니다 .

init(...) {

    // ... your initialization code ...

    super.init(frame: .zero)
    layer.addSublayer(shadowLayer)
}

public override func layoutSubviews() {
    super.layoutSubviews()
    shadowLayer.frame = bounds
}

InnerShadowLayer 이행

/// Shadow is a struct defining the different kinds of shadows
public struct Shadow {
    let x: CGFloat
    let y: CGFloat
    let blur: CGFloat
    let opacity: CGFloat
    let color: UIColor
}

/// A layer that applies an inner shadow to the specified edges of either its path or its bounds
public class InnerShadowLayer: CALayer {
    private let shadow: Shadow
    private let edge: UIRectEdge

    public init(shadow: Shadow, edge: UIRectEdge) {
        self.shadow = shadow
        self.edge = edge
        super.init()
        setupShadow()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func layoutSublayers() {
        updateShadow()
    }

    private func setupShadow() {
        shadowColor = shadow.color.cgColor
        shadowOpacity = Float(shadow.opacity)
        shadowRadius = shadow.blur / 2.0
        masksToBounds = true
    }

    private func updateShadow() {
        shadowOffset = {
            let topWidth: CGFloat = 0
            let leftWidth = edge.contains(.left) ? shadow.y / 2 : 0
            let bottomWidth: CGFloat = 0
            let rightWidth = edge.contains(.right) ? -shadow.y / 2 : 0

            let topHeight = edge.contains(.top) ? shadow.y / 2 : 0
            let leftHeight: CGFloat = 0
            let bottomHeight = edge.contains(.bottom) ? -shadow.y / 2 : 0
            let rightHeight: CGFloat = 0

            return CGSize(width: [topWidth, leftWidth, bottomWidth, rightWidth].reduce(0, +),
                          height: [topHeight, leftHeight, bottomHeight, rightHeight].reduce(0, +))
        }()

        let insets = UIEdgeInsets(top: edge.contains(.top) ? -bounds.height : 0,
                                  left: edge.contains(.left) ? -bounds.width : 0,
                                  bottom: edge.contains(.bottom) ? -bounds.height : 0,
                                  right: edge.contains(.right) ? -bounds.width : 0)
        let path = UIBezierPath(rect: bounds.inset(by: insets))
        let cutout = UIBezierPath(rect: bounds).reversing()
        path.append(cutout)
        shadowPath = path.cgPath
    }
}

1

이 코드는 나를 위해 일했습니다.

class InnerDropShadowView: UIView {
    override func draw(_ rect: CGRect) {
        //Drawing code
        let context = UIGraphicsGetCurrentContext()
        //// Shadow Declarations
        let shadow: UIColor? = UIColor.init(hexString: "a3a3a3", alpha: 1.0) //UIColor.black.withAlphaComponent(0.6) //UIColor.init(hexString: "d7d7da", alpha: 1.0)
        let shadowOffset = CGSize(width: 0, height: 0)
        let shadowBlurRadius: CGFloat = 7.5
        //// Rectangle Drawing
        let rectanglePath = UIBezierPath(rect: bounds)
        UIColor.groupTableViewBackground.setFill()
        rectanglePath.fill()
        ////// Rectangle Inner Shadow
        context?.saveGState()
        UIRectClip(rectanglePath.bounds)
        context?.setShadow(offset: CGSize.zero, blur: 0, color: nil)
        context?.setAlpha((shadow?.cgColor.alpha)!)
        context?.beginTransparencyLayer(auxiliaryInfo: nil)
        do {
            let opaqueShadow: UIColor? = shadow?.withAlphaComponent(1)
            context?.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: opaqueShadow?.cgColor)
            context!.setBlendMode(.sourceOut)
            context?.beginTransparencyLayer(auxiliaryInfo: nil)
            opaqueShadow?.setFill()
            rectanglePath.fill()
            context!.endTransparencyLayer()
        }
        context!.endTransparencyLayer()
        context?.restoreGState()
    }
}

0

몇 가지 코드가 여기 당신을 위해이 작업을 수행 할 수 있습니다. 뷰의 레이어를 (재정 의하여 + (Class)layerClass) JTAInnerShadowLayer로 변경하면 init 메서드에서 들여 쓰기 레이어의 내부 그림자를 설정할 수 있으며 작업을 수행합니다. 원본 콘텐츠도 그리려면 setDrawOriginalImage:yes들여 쓰기 레이어 를 호출해야합니다 . 이것이 어떻게 작동하는지에 대한 블로그 게시물이 있습니다 .


@MiteshDobareeya 방금 두 링크를 모두 테스트했는데 제대로 작동하는 것처럼 보입니다 (비공개 탭 포함). 어떤 링크가 문제를 일으켰습니까?
제임스 스 누크

이 내부 섀도우 코드 구현을 살펴볼 수 있습니까? ViewDidAppear 메서드에서만 작동합니다. 그리고 약간의 깜박임을 보여줍니다. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya

0

그라디언트 레이어 사용 :

UIView * mapCover = [UIView new];
mapCover.frame = map.frame;
[view addSubview:mapCover];

CAGradientLayer * vertical = [CAGradientLayer layer];
vertical.frame = mapCover.bounds;
vertical.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[UIColor whiteColor].CGColor, nil];
vertical.locations = @[@0.01,@0.1,@0.9,@0.99];
[mapCover.layer insertSublayer:vertical atIndex:0];

CAGradientLayer * horizontal = [CAGradientLayer layer];
horizontal.frame = mapCover.bounds;
horizontal.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[UIColor whiteColor].CGColor, nil];
horizontal.locations = @[@0.01,@0.1,@0.9,@0.99];
horizontal.startPoint = CGPointMake(0.0, 0.5);
horizontal.endPoint = CGPointMake(1.0, 0.5);
[mapCover.layer insertSublayer:horizontal atIndex:0];

0

간단한 해결책이 있습니다. 일반적인 그림자를 그리고 이렇게 회전하면됩니다.

@objc func shadowView() -> UIView {
        let shadowView = UIView(frame: .zero)
        shadowView.backgroundColor = .white
        shadowView.layer.shadowColor = UIColor.grey.cgColor
        shadowView.layer.shadowOffset = CGSize(width: 0, height: 2)
        shadowView.layer.shadowOpacity = 1.0
        shadowView.layer.shadowRadius = 4
        shadowView.layer.compositingFilter = "multiplyBlendMode"
        return shadowView
    }

func idtm_addBottomShadow() {
        let shadow = shadowView()
        shadow.transform = transform.rotated(by: 180 * CGFloat(Double.pi))
        shadow.transform = transform.rotated(by: -1 * CGFloat(Double.pi))
        shadow.translatesAutoresizingMaskIntoConstraints = false
        addSubview(shadow)
        NSLayoutConstraint.activate([
            shadow.leadingAnchor.constraint(equalTo: leadingAnchor),
            shadow.trailingAnchor.constraint(equalTo: trailingAnchor),
            shadow.bottomAnchor.constraint(equalTo: bottomAnchor),
            shadow.heightAnchor.constraint(equalToConstant: 1),
            ])
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.