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

다음 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 객체 위에 레이어를 추가하므로 어떻게 해야할지 모르겠습니다.

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

여기에 이미지 설명 입력

감사합니다 …



답변

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. 그런 다음 큰 모양에 구멍을 채우십시오. 색상은 중요하지 않습니다. 모든 것을 올바르게 수행했다면이 색상이 아닌 그림자 만 볼 수 있기 때문입니다.

답변

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

신용이 필요한 곳에 신용을 부여하기 위해 이것은 본질적으로 더 큰 지역에서 더 작은 지역을 빼는 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:합니다.


답변

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

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

대체 텍스트

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의 프레임을 설정하면 그림자가 제대로 늘어납니다.


답변

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 레이어는 그림자 앞에 렌더링되므로 불투명 한 배경색이 없어야합니다.


답변

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

  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의 설명을 참조하십시오 .


답변

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

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

-(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];
}


답변

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에 대한 하단 내부 그림자를 추가합니다.