Эффект внутренней тени на слое 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

Для всех, кто интересуется, как нарисовать внутреннюю тень с помощью Core Graphics в соответствии с предложением Costique, тогда вот как: (на 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. Теперь создайте прямоугольник большего размера, ограничивающий видимый путь. Прежде чем закрыть этот путь, добавьте видимый путь. Затем закройте контур, чтобы создать фигуру, из которой вычтена видимая линия. Возможно, вы захотите изучить методы заполнения (ненулевое обмотка четного / нечетного) в зависимости от того, как вы создали эти пути. По сути, чтобы заставить подпути «вычитаться», когда вы складываете их вместе, вам нужно нарисовать их (или, скорее, построить) в противоположных направлениях, одно по часовой стрелке, а другое - против часовой стрелки.
  4. Затем вам нужно установить видимый путь в качестве обтравочного контура в контексте, чтобы вы не рисовали на экране ничего за его пределами.
  5. Затем настройте тень в контексте, включая смещение, размытие и цвет.
  6. Затем заполните большую форму отверстием. Цвет не имеет значения, потому что, если вы все сделали правильно, вы не увидите этого цвета, только тень.
Дэниел Торп
источник
Спасибо, а можно ли настроить радиус? В настоящее время он основан на границах, но я хотел бы вместо этого основывать на заданном радиусе (например, 5.0f). В приведенном выше коде он слишком округлен.
runmad
2
@runmad Что ж, вы можете создать любой видимый CGPath, какой захотите, использованный здесь пример - это просто пример, выбранный для краткости. Если вы хотите создать прямоугольник с закругленными углами, вы можете просто сделать что-то вроде: CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect: rect cornerRadius: radius] .CGPath Надеюсь, что это поможет.
Дэниэл Торп,
4
@DanielThorpe: +1 за хороший ответ. Я исправил код закругленного прямоугольного пути (ваш сломался при изменении радиуса) и упростил код внешнего прямоугольного пути. Надеюсь, ты не против.
Regexident
Как правильно настроить внутреннюю тень с 4-х сторон, а не только с 2?
Protocole
@Protocole вы можете установить смещение на {0,0}, но используйте радиус тени, скажем, 4.f.
Daniel Thorpe
47

Я знаю, что опаздываю на эту вечеринку, но это помогло бы мне найти в начале моих путешествий ...

Чтобы отдать должное, где следует отметить, это, по сути, модификация разработки Дэниела Торпа решения Costique по вычитанию меньшей области из большей области. Эта версия предназначена для тех, кто использует композицию слоев вместо переопределения-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]самый простой выбор.
Мэтт Уилдинг
Приветствую, Мэтт :-)
Мо
Я получаю черный (внешний) прямоугольник для shadowLayer.path, который правильно рисует внутреннюю тень. Как от него избавиться (черный внешний прямоугольник)? Похоже, вы можете установить fillColor только внутри контекста, и вы его не используете.
Оливье
11
Это прекрасно работает! Загрузил на github с некоторыми дополнениями. Попробуй :) github.com/inamiy/YIInnerShadowView
inamiy
35

Можно нарисовать внутреннюю тень с помощью Core Graphics, сделав большой прямоугольный путь за пределами границ, вычтя прямоугольный путь размером с границы и заполнив полученный путь «нормальной» тенью.

Однако, поскольку вам нужно объединить его со слоем градиента, я думаю, что более простым решением является создание прозрачного PNG-изображения внутренней тени из 9 частей и растягивания его до нужного размера. Изображение тени из 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, и он должен правильно растянуть тень.

Costique
источник
Да, полагаю, ты прав. Просто хотел, чтобы слой был как можно более плоским. Я мог бы создать изображение в Photoshop с внутренней тенью и градиентом, просто у меня проблемы с 100% совпадением цветов на устройстве при использовании изображения.
runmad
Да, это проблема всех градиентов и теней, я просто не могу воспроизвести эти эффекты Photoshop 1: 1 на iOS, как ни стараюсь.
Costique,
31

Упрощенная версия с использованием только CALayer в 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)
    }
}

Обратите внимание, что слой innerShadow не должен иметь непрозрачного цвета фона, поскольку он будет отображаться перед тенью.

Патрик Пийнаппель
источник
Последняя строка содержит «слой». Откуда это взялось?
Чарли Селигман
@CharlieSeligman Это родительский слой, которым может быть любой слой. Вы можете использовать собственный слой или слой представления (UIView имеет свойство слоя).
Patrick Pijnappel
должно быть let innerShadow = CALayer(); innerShadow.frame = bounds. Без правильных границ он не смог бы нарисовать правильную тень. В любом случае спасибо
haik.ampardjian
@noir_eagle Верно, хотя вы, вероятно, захотите установить это, layoutSubviews()чтобы синхронизировать
Патрик Пийнаппель
Правильно! Либо в, layoutSubviews()либо вdraw(_ rect)
haik.ampardjian
24

Немного обходной путь, но он позволяет избежать использования изображений (читай: легко изменить цвета, радиус тени и т.д.), и это всего лишь несколько строк кода.

  1. Добавьте UIImageView в качестве первого подвида UIView, на котором должна отображаться тень. Я использую 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] не работает. В этом примере я использую другой цвет, но вы можете изменить его, чтобы он имел тот же цвет, что и начало тени. :)

См. Комментарий bbrame о UIColorFromRGBмакросе ниже.

джинглестхула
источник
Я оставил это, но предполагаю, что вы сделаете это как часть добавления изображения - обязательно установите рамку в тот же прямоугольник, что и родительский UIView. Если вы используете IB, установите стойки и пружины так, чтобы иметь размер тени с видом, если вы будете изменять рамку родительского вида. В коде должна быть маска изменения размера, которую вы можете ИЛИ сделать то же самое, AFAIK.
jinglesthula
Сейчас это самый простой способ, но имейте в виду, что теневые методы CALayer доступны только в iOS 3.2 и новее. Я поддерживаю 3.1, поэтому я окружаю установку этих атрибутов в if ([layer responseToSelector: @selector (setShadowColor :)]) {
DougW
Похоже, у меня это не работает. По крайней мере, на xcode 4.2 и ios simulator 4.3. Чтобы тень появилась, мне нужно добавить цвет фона ... в этот момент тень появляется только снаружи.
Андреа
@Andrea - имейте в виду оговорку, о которой я упоминал выше. Я думаю, что цвет фона или рамка могут иметь такой же эффект, как «дать ему что-то для добавления тени». Что касается того, что он появляется снаружи, если UIImageView не является подвидом того, на котором вам нужна внутренняя тень, это может быть оно - мне пришлось бы посмотреть на ваш код, чтобы увидеть.
jinglesthula
Просто чтобы исправить мое предыдущее утверждение ... код действительно работает ... Мне что-то не хватало, но, к сожалению, я не могу вспомнить это прямо сейчас. :) Итак ... спасибо, что поделились этим фрагментом кода.
Андреа
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
источник
@SomaMan можно ли установить тень только с определенной стороны? Как только вверху или вверху / внизу или вверху / справа и т. Д.
Mitesh Dobareeya
8

Вместо того, чтобы рисовать внутреннюю тень с помощью drawRect или добавить UIView в View. Вы можете напрямую добавить CALayer к границе, например: если мне нужен эффект внутренней тени на нижней части 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];

Это добавит нижнюю внутреннюю тень для целевого UIView

Джерри Чжан
источник
6

Вот вариант быстрой смены startPointи endPointвнесения изменений с каждой стороны.

        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, поскольку я поставлял статическую библиотеку и НИКАКИХ ресурсов ... Я использовал это для экран ввода ПИН-кода из четырех экземпляров UITextField, которые могут отображать один необработанный символ или (BOOL) [self isUsingBullets] или (BOOL) [self usingAsterisks] в ViewController. Приложение предназначено для iPhone / iPhone Retina / iPad / iPad Retina, поэтому мне не нужно предоставлять четыре изображения ...

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

Надеюсь, это кому-то поможет, так как этот форум помог мне.

Foxnolds
источник
3

Посмотрите замечательную статью Криса Эмери Inner Shadows in Quartz , в которой объясняется, как отрисовываются внутренние тени с помощью 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);
}
Voiger
источник
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)
      }
    } 
  }
}
Arco
источник
Что, если я не использую его как отдельный класс, но, как и в моем коде, контекст (ctx) равен нулю, когда я получаю это:let ctx = UIGraphicsGetCurrentContext
Мохсин Хубайб Ахмед 01
@MohsinKhubaibAhmed Вы можете получить текущий контекст по методу UIGraphicsGetCurrentContext для извлечения, когда некоторые представления помещают свой контекст в стек.
Arco
@Arco У меня возникли проблемы, когда я повернул устройство. Я добавил «переопределить удобство init (layer: Any) {self.init ()}». Теперь ошибка не отображается!
Yuma Technical Inc.
Добавлен init (layer: Any) для исправления сбоя.
Ник Ков
2

Масштабируемое решение с использованием CALayer в Swift

С помощью описанного 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, вы можете установить внутреннюю тень на уровне отступа в своем методе инициализации, и он сделает всю работу за вас. Если вы также хотите нарисовать исходный контент, убедитесь, что вы вызываете 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),
            ])
    }
яго849
источник