Внутрішній ефект тіні на шар 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 за приємну відповідь. Я виправив закруглений код прямого шляху (ваш зламався при зміні радіуса) і спростив зовнішній код прямого шляху. Сподіваюся, ви не проти.
Регексидент

Як я можу правильно налаштувати внутрішню тінь з 4 напрямків, а не лише з 2?
Protocole

@Protocole, ви можете встановити зсув на {0,0}, але використовуйте радіус тіні, скажімо, 4.f.
Даніель Торп

47

Я знаю, що запізнився на цю вечірку, але це допомогло б мені знайти на початку подорожей ...

Щоб надати кредит там, де кредит належить, це, по суті, модифікація розробки Даніеля Торпа щодо рішення Костіка щодо віднімання меншого регіону від більшого. Ця версія призначена для тих, хто використовує склад шару замість заміни-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? Не могли б ви пояснити це ще трохи, будь ласка.
Мо

4
@Moe Це може бути будь-який довільний CGPath, який ви хочете. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]будучи найпростішим вибором.
Matt Wilding

Вітаємо за цього Метта :-)
Мо

Я отримую чорний (зовнішній) прямокутник для shadowLayer.path, який правильно малює внутрішню тінь. Як я можу від нього позбутися (чорний зовнішній прямокутник)? Схоже, ви можете встановити fillColor лише в контексті, і ви не використовуєте його.
Олів'є

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

Так, це проблема з усіма градієнтами і тінями, я просто не можу відтворити ці ефекти Photoshop 1: 1 на iOS, наскільки я намагаюся.
Costique

29

Спрощена версія, що використовує лише 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 ([шар відповідаєToSelector: @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 чи можна встановити тінь лише з певної сторони? Як тільки вгорі або вгорі / внизу або вгорі / справа тощо.
Mitesh Dobareeya

8

Замість того, щоб малювати внутрішню тінь за допомогою drawRect або додайте UIView до подання. Ви можете безпосередньо додати 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

Ось версія swift, change 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, оскільки постачав статичну бібліотеку та НІЯКИХ ресурсів ... Я використовував це для екран введення PIN-коду з чотирьох екземплярів 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];
}

Сподіваюся, це комусь допомагає, як цей форум допоміг мені.


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) дорівнює нулю, коли я отримую таке:let ctx = UIGraphicsGetCurrentContext
Мохсін Хубайб Ахмед

@MohsinKhubaibAhmed Ви можете отримати поточний контекст методом UIGraphicsGetCurrentContext для отримання, коли деякі представлення висувають свій контекст у стек.
Arco

@Arco У мене виникли проблеми, коли я повернув пристрій. Я додав 'перевизначити зручність init (шар: Будь-який) {self.init ()}'. Тепер жодної помилки не відображається!
Yuma Technical Inc.

Додано init (шар: Any) для виправлення аварійного завершення роботи.
Нік Ков,

2

Масштабоване рішення за допомогою CALayer в Swift

З описаним InnerShadowLayer ви також можете увімкнути внутрішні тіні лише для певних країв, виключаючи інші. (наприклад, ви можете активувати внутрішні тіні лише на лівому та верхньому краях вашого виду)

Потім ви можете додати a 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
Мітеш Доберея

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),
            ])
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.