Hiệu ứng bóng bên trong trên lớp UIView?


92

Tôi có CALayer sau:

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

Tôi muốn thêm hiệu ứng bóng bên trong vào nó, nhưng tôi không chắc chắn về cách thực hiện điều này. Tôi cho rằng tôi sẽ được yêu cầu vẽ trong drawRect, tuy nhiên điều này sẽ thêm lớp lên trên các đối tượng UIView khác, vì nó được cho là một thanh phía sau một số nút, vì vậy tôi không biết phải làm gì?

Tôi có thể thêm một lớp khác, nhưng một lần nữa, không chắc làm thế nào để đạt được hiệu ứng bóng bên trong (như thế này:

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

Trợ giúp được đánh giá cao ...

Câu trả lời:


108

Đối với bất kỳ ai khác tự hỏi làm thế nào để vẽ một bóng bên trong bằng Core Graphics theo gợi ý của Costique, thì đây là cách: (trên iOS điều chỉnh khi cần thiết)

Trong phương thức 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);

Vì vậy, về cơ bản có các bước sau:

  1. Tạo con đường của bạn
  2. Đặt màu tô bạn muốn, thêm đường dẫn này vào ngữ cảnh và điền vào ngữ cảnh
  3. Bây giờ, hãy tạo một hình chữ nhật lớn hơn có thể ràng buộc đường dẫn nhìn thấy được. Trước khi đóng đường dẫn này, hãy thêm đường dẫn hiển thị. Sau đó, đóng đường dẫn, để bạn tạo một hình dạng với đường dẫn nhìn thấy được trừ đi. Bạn có thể muốn điều tra các phương pháp lấp đầy (cuộn không của chẵn / lẻ) tùy thuộc vào cách bạn tạo các đường dẫn này. Về bản chất, để các đường con "trừ" khi bạn cộng chúng lại với nhau, bạn cần vẽ chúng (hay đúng hơn là dựng chúng) theo các hướng ngược nhau, một chiều theo chiều kim đồng hồ và ngược chiều kim đồng hồ.
  4. Sau đó, bạn cần đặt đường dẫn hiển thị của mình làm đường cắt trên ngữ cảnh để bạn không vẽ bất cứ thứ gì bên ngoài nó ra màn hình.
  5. Sau đó, thiết lập bóng trên bối cảnh, bao gồm bù đắp, làm mờ và màu sắc.
  6. Sau đó, lấp đầy hình dạng lớn với lỗ trong đó. Màu sắc không quan trọng, bởi vì nếu bạn đã làm đúng mọi thứ, bạn sẽ không thấy màu này, chỉ là bóng.

Cảm ơn, nhưng có thể điều chỉnh bán kính không? Nó hiện đang dựa trên các giới hạn, nhưng thay vào đó tôi muốn dựa trên một bán kính đã đặt (như 5.0f). Với đoạn mã trên, nó làm tròn quá nhiều.
runmad

2
@runmad Chà, bạn có thể tạo bất kỳ loại CGPath hiển thị nào mà bạn muốn, ví dụ được sử dụng ở đây chỉ là ví dụ được chọn cho ngắn gọn. Nếu bạn muốn tạo một hình chữ nhật tròn, thì bạn có thể thực hiện một số thao tác như: CGPath opensPath = [UIBezierPath bezierPathWithRoundsRect: direct angleRadius: radius] .CGPath Hy vọng điều đó sẽ hữu ích.
Daniel Thorpe

4
@DanielThorpe: +1 cho câu trả lời hay. Tôi đã sửa mã đường dẫn chữ nhật tròn (của bạn bị hỏng khi thay đổi bán kính) và đơn giản hóa mã đường dẫn chữ nhật bên ngoài. Mong bạn không phiền lòng.
Regexident

Làm cách nào để thiết lập bóng bên trong đúng cách từ 4 hướng, không chỉ 2 hướng?
Protocole

@Protocole, bạn có thể đặt độ lệch thành {0,0}, nhưng hãy sử dụng bán kính bóng mờ, chẳng hạn, 4.f.
Daniel Thorpe

47

Tôi biết tôi đến muộn bữa tiệc này, nhưng điều này sẽ giúp tôi tìm lại sớm trong chuyến đi của mình ...

Để cấp tín dụng khi tín dụng đến hạn, đây về cơ bản là một sửa đổi trong công trình của Daniel Thorpe về giải pháp Costique trừ một vùng nhỏ hơn cho một vùng lớn hơn. Phiên bản này dành cho những người sử dụng thành phần lớp thay vì ghi đè-drawRect:

Các CAShapeLayerlớp có thể được sử dụng để đạt được hiệu quả tương tự:

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

Tại thời điểm này, nếu lớp mẹ của bạn không che đến giới hạn của nó, bạn sẽ thấy vùng thừa của lớp mặt nạ xung quanh các cạnh của lớp. Đây sẽ là 42 pixel màu đen nếu bạn chỉ sao chép trực tiếp ví dụ. Để loại bỏ nó, bạn chỉ cần sử dụng một đường khác CAShapeLayercó cùng đường dẫn và đặt nó làm mặt nạ của lớp bóng:

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

Tôi chưa tự đánh giá điều này, nhưng tôi nghi ngờ rằng việc sử dụng phương pháp này kết hợp với rasterization hiệu quả hơn là ghi đè -drawRect:.


3
someInnerPath? Bạn có thể giải thích thêm một chút được không.
Moe

4
@Moe Nó có thể là bất kỳ CGPath tùy ý nào bạn muốn. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]là sự lựa chọn đơn giản nhất.
Matt Wilding vào

Chúc mừng cho điều đó Matt :-)
Moe

Tôi nhận được một hình chữ nhật màu đen (bên ngoài) cho shadowLayer.path để vẽ chính xác bóng bên trong. Làm cách nào để loại bỏ nó (hình chữ nhật bên ngoài màu đen)? Có vẻ như bạn chỉ có thể đặt FillColor bên trong một ngữ cảnh và bạn không sử dụng nó.
Olivier

11
Điều này hoạt động rất tốt! Tôi đã tải lên github với một số bổ sung. Có một thử :) github.com/inamiy/YIInnerShadowView
inamiy

35

Có thể vẽ bóng bên trong bằng Core Graphics bằng cách tạo một đường dẫn hình chữ nhật lớn bên ngoài đường viền, trừ một đường dẫn hình chữ nhật có kích thước giới hạn và lấp đầy đường dẫn kết quả bằng một bóng "bình thường".

Tuy nhiên, vì bạn cần kết hợp nó với một lớp gradient, tôi nghĩ giải pháp dễ dàng hơn là tạo một hình ảnh PNG trong suốt gồm 9 phần của bóng bên trong và kéo nó ra đúng kích thước. Hình ảnh bóng gồm 9 phần sẽ trông như thế này (kích thước của nó là 21x21 pixel):

văn bản thay thế

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

Sau đó, thiết lập khung của innerShadowLayer và nó sẽ kéo căng bóng đúng cách.


Vâng, tôi cho rằng bạn đúng. Chỉ muốn lớp càng phẳng càng tốt. Tôi có thể tạo hình ảnh trong Photoshop với bóng bên trong và giao diện gradient, tôi chỉ gặp vấn đề với màu sắc phù hợp 100% trên thiết bị khi sử dụng hình ảnh.
runmad

Đúng, đó là một vấn đề với tất cả các độ dốc và bóng đổ, tôi chỉ không thể tái tạo các hiệu ứng Photoshop này 1: 1 trên iOS, dù tôi đã thử.
Costique

29

Một phiên bản đơn giản chỉ sử dụng CALayer, trong Swift:

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

Lưu ý rằng lớp innerShadow không được có màu nền mờ đục vì nó sẽ hiển thị phía trước bóng đổ.


Dòng cuối cùng chứa 'lớp'. Trường hợp nào này đến từ đâu?
Charlie Seligman

@CharlieSeligman Đó là lớp mẹ, có thể là bất kỳ lớp nào. Bạn có thể sử dụng một lớp tùy chỉnh hoặc lớp của chế độ xem (UIView có thuộc tính lớp).
Patrick Pijnappel

nên được let innerShadow = CALayer(); innerShadow.frame = bounds. Nếu không có giới hạn thích hợp, nó sẽ không vẽ bóng thích hợp. Thanks anyway
haik.ampardjian

@noir_eagle Đúng vậy, mặc dù có thể bạn muốn thiết lập rằng trong layoutSubviews()để giữ cho nó đồng bộ
Patrick Pijnappel

Đúng! Hoặc trong layoutSubviews()hoặc ởdraw(_ rect)
haik.ampardjian

24

Hơi vòng vo, nhưng nó tránh phải sử dụng hình ảnh (đọc: dễ thay đổi màu sắc, bán kính bóng, v.v.) và nó chỉ có một vài dòng mã.

  1. Thêm UIImageView làm lượt xem phụ đầu tiên của UIView mà bạn muốn dropshadow trên đó. Tôi sử dụng IB, nhưng bạn có thể làm như vậy theo chương trình.

  2. Giả sử tham chiếu đến UIImageView là '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];

Lưu ý: Bạn phải có đường viền, nếu không bóng tối sẽ không hiển thị. [UIColor clearColor] không hoạt động. Trong ví dụ, tôi sử dụng một màu khác, nhưng bạn có thể đánh rối nó để nó có cùng màu với phần đầu của bóng đổ. :)

Xem bình luận của bbrame bên dưới về UIColorFromRGBmacro.


Tôi đã bỏ qua nhưng giả sử bạn làm điều này như là một phần của việc thêm chế độ xem hình ảnh - hãy đảm bảo đặt khung thành cùng một chữ cái với UIView chính. Nếu bạn đang sử dụng IB, hãy đặt các thanh chống và lò xo ngay để có kích thước bóng với chế độ xem nếu bạn sẽ thay đổi khung của chế độ xem chính. Trong mã phải có một mặt nạ thay đổi kích thước mà bạn có thể HOẶC làm tương tự, AFAIK.
jinglesthula

Đây là cách dễ nhất hiện nay, nhưng hãy lưu ý rằng phương pháp bóng CALayer chỉ khả dụng trong iOS 3.2 trở lên. Tôi hỗ trợ 3.1, vì vậy tôi đặt xung quanh việc thiết lập các thuộc tính này trong if ([layer responseToSelector: @selector (setShadowColor :)]) {
DougW

Điều này dường như không hiệu quả với tôi. Ít nhất trên xcode 4.2 và ios simulator 4.3. Để làm cho bóng xuất hiện, tôi phải thêm một màu nền ... tại thời điểm đó, giọt bóng chỉ xuất hiện bên ngoài.
Andrea

@Andrea - hãy ghi nhớ cảnh báo mà tôi đã đề cập ở trên. Tôi nghĩ rằng màu nền hoặc đường viền có thể có cùng tác dụng 'tạo cho nó thứ gì đó để thêm bóng vào'. Đối với việc nó xuất hiện bên ngoài, nếu UIImageView không phải là một chế độ xem phụ của cái bạn muốn có bóng bên trong thì có thể là nó - tôi sẽ phải nhìn vào mã của bạn để xem.
jinglesthula,

Chỉ để sửa lại tuyên bố trước đây của tôi ... mã thực sự hoạt động ... Tôi đã thiếu một cái gì đó nhưng tiếc là tôi không thể nhớ nó ngay bây giờ. :) Vì vậy, ... cảm ơn vì đã chia sẻ đoạn mã này.
Andrea

17

Muộn còn hơn không...

Đây là một cách tiếp cận khác, có thể không tốt hơn bất kỳ cách nào đã được đăng, nhưng nó hay và đơn giản -

-(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 có thể chỉ đặt bóng với mặt cụ thể không? Giống như chỉ ở trên hoặc trên / dưới hoặc trên / phải vv ..
Mitesh Dobareeya

8

Thay vì vẽ bóng bên trong bằng drawRect hoặc thêm UIView vào View. Bạn có thể Thêm trực tiếp CALayer vào đường viền, ví dụ: nếu tôi muốn có hiệu ứng bóng bên trong trên đáy của UIView V.

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

Điều này thêm một bóng tối bên trong dưới cùng cho UIView mục tiêu


6

Đây là một phiên bản của nhanh chóng, thay đổi startPointendPointđể làm cho nó ở mỗi bên.

        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)

Đã làm cho tôi !! Cảm ơn bạn.
iUser

5

Đây là giải pháp của bạn, mà tôi đã xuất từ 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

Tôi đến rất muộn bữa tiệc nhưng tôi muốn trả lại cho cộng đồng .. Đây là phương pháp tôi đã viết để xóa Hình ảnh nền UITextField khi tôi đang cung cấp Thư viện tĩnh và KHÔNG có Tài nguyên ... Tôi đã sử dụng phương pháp này để màn hình Nhập mã PIN gồm bốn trường hợp UITextField có thể hiển thị Một ký tự thô hoặc (BOOL) [self isUsingBullets] hoặc (BOOL) [self usingAsterisks] trong ViewController. Ứng dụng dành cho iPhone / iPhone retina / iPad / iPad Retina nên tôi không phải cung cấp bốn hình ảnh ...

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

Tôi hy vọng điều này sẽ giúp ai đó vì diễn đàn này đã giúp tôi.


3

Hãy xem bài viết tuyệt vời của Inner Shadows in Quartz của Chris Emery wich giải thích cách các bóng bên trong được vẽ bởi PaintCode và đưa ra một đoạn mã rõ ràng và gọn gàng:

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

Đây là giải pháp của tôi trong Swift 4.2. Bạn có muốn thử không?

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

Điều gì sẽ xảy ra nếu tôi không sử dụng nó như một lớp riêng biệt, nhưng giống như sử dụng trong mã của tôi, ngữ cảnh (ctx) là 0 khi tôi nhận được điều này:let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed Bạn có thể lấy ngữ cảnh hiện tại bằng phương pháp UIGraphicsGetCurrentContext để tìm nạp khi một số chế độ xem đẩy ngữ cảnh của chúng lên ngăn xếp.
Arco

@Arco Tôi đã gặp một số sự cố khi xoay thiết bị. Tôi đã thêm 'ghi đè tiện ích init (lớp: Bất kỳ) {self.init ()}'. Bây giờ không có lỗi hiển thị!
Yuma Technical Inc.

Đã thêm init (lớp: Bất kỳ) để sửa lỗi.
Nik Kov

2

Giải pháp có thể mở rộng bằng cách sử dụng CALayer trong Swift

Với mô tả InnerShadowLayer bạn cũng có thể bật bóng bên trong chỉ cho các cạnh cụ thể, không bao gồm các cạnh khác. (ví dụ: bạn chỉ có thể bật bóng bên trong ở các cạnh bên trái và trên cùng của chế độ xem của bạn)

Sau đó, bạn có thể thêm một InnerShadowLayervào chế độ xem của mình bằng cách sử dụng:

init(...) {

    // ... your initialization code ...

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

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

InnerShadowLayer thực hiện

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

mã này làm việc cho tôi

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

Có một số mã ở đây có thể làm điều này cho bạn. Nếu bạn thay đổi lớp trong chế độ xem của mình (bằng cách ghi đè + (Class)layerClass), thành JTAInnerShadowLayer thì bạn có thể đặt bóng bên trong trên lớp thụt lề trong phương thức init của bạn và nó sẽ thực hiện công việc cho bạn. Nếu bạn cũng muốn vẽ nội dung gốc, hãy đảm bảo bạn gọi setDrawOriginalImage:yeslớp thụt lề. Có một bài đăng trên blog về cách hoạt động của nó ở đây .


@MiteshDobareeya Vừa kiểm tra cả hai liên kết và chúng dường như hoạt động tốt (kể cả trong tab riêng tư). Liên kết nào đã gây ra sự cố cho bạn?
James Snook

Bạn có thể vui lòng xem xét việc triển khai mã bóng bên trong này không. Nó chỉ hoạt động trong phương thức ViewDidAppear. Và cho thấy một số nhấp nháy. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya 27/03/18

0

Sử dụng lớp Gradient:

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

Có một giải pháp đơn giản - chỉ cần vẽ bóng bình thường và xoay, như thế này

@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),
            ])
    }
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.