Як намалювати тінь під UIView?


352

Я намагаюся намалювати тінь під нижньою межею UIViewв Cocoa Touch. Я розумію, що мені слід використовувати CGContextSetShadow()тіні, але посібник з програмування Quartz 2D трохи розпливчастий:

  1. Збережіть стан графіки.
  2. Викличте функцію CGContextSetShadow, передаючи відповідні значення.
  3. Виконайте весь малюнок, на який потрібно нанести тіні.
  4. Відновлення стану графіки

Я спробував наступне в UIViewпідкласі:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

.. але це не працює для мене, і я трохи застряг щодо (а) куди йти далі і (б) якщо є щось, що мені потрібно зробити, UIViewщоб зробити цю роботу?

Відповіді:


98

У вашому поточному коді ви зберігаєте GStateпоточний контекст, налаштовуєте його, щоб намалювати тінь .. і відновите його таким, яким він був до того, як ви налаштували його, щоб намалювати тінь. Потім, нарешті, ви посилаєтесь на реалізацію суперкласуdrawRect :.

Будь-який малюнок, на який має вплинути налаштування тіней, має відбуватися після

CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

але раніше

CGContextRestoreGState(currentContext);

Тож якщо ви хочете, щоб суперкласс drawRect:був "загорнутий" у тінь, то як тоді, якщо ви переставляєте код таким чином?

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    [super drawRect: rect];
    CGContextRestoreGState(currentContext);
}

789

Набагато простішим підходом є встановлення деяких атрибутів шару подання про ініціалізацію:

self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Вам потрібно імпортувати QuartzCore.

#import <QuartzCore/QuartzCore.h>

4
Але майте на увазі, що це працює лише на iOS 3.2+, тому якщо ваш додаток повинен працювати на старій версії, ви повинні використовувати рішення Крістіана або статичне зображення позаду перегляду, якщо це варіант.
florianbuerger

119
Це рішення також потребує додавання #import <QuartzCore/QuartzCore.h>"до файлу .h.
MusiGenesis

26
Установка masksToBoundsдля NOзведе нанівець cornerRadius, немає?
pixelfreak

4
Гаразд, щоб вирішити це, фонColor потрібно встановити на шарі, а перегляд повинен бути прозорим.
pixelfreak

@pixelfreak Як це зробити? Я намагався, self.layer.backgroundColor = [[UIColor whiteColor] CGColor];але не пощастило. Який погляд повинен бути прозорим?
Віктор Ван Хе

232
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Це уповільнить додаток. Додавання наступного рядка може підвищити ефективність, якщо ваш погляд видимо прямокутний:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;

1
Напевно, варто відзначити, що ця оптимізація корисна лише в тому випадку, якщо ваш погляд видимо прямокутний.
Бенджамін Добелл

1
self.layer.shadowPath ... замість чого? або просто додати до нього
Chrizzz,

2
Просто додайте додатковий рядок на додаток до інших.
Крістоферкоттон

11
@NathanGaskin - малювання тіней - це дорога операція, тому, наприклад, якщо ваша програма дозволяє інша інтерфейсна орієнтація і ви почнете обертати пристрій, не чітко вказуючи шлях тіні, він повинен бути виведений кілька разів під час тієї анімації, яка буде залежно від залежності форма, ймовірно, помітно сповільнює анімацію
Пітер Пайчл

9
@BenjaminDobell Цей конкретний рядок працює лише для прямокутників, але ви також можете створювати не прямокутні шляхи. Наприклад, якщо у вас округлий прямокутник, ви можете використовувати bezierPathWithRoundedRect: cornerRadius:
Dan Dyer

160

Це ж рішення, але просто для нагадування: Ви можете визначити тінь безпосередньо на дошці.

Наприклад:

введіть тут опис зображення


2
На жаль, я думаю, що CGColor є недоступним для розкадрівки :(
Antzi

1
просто визначте категорію для UIViewабо CGLayerяка є UIColorобгорткою для CGColorвластивості;)
DeFrenZ

1
Це посилання описує, як це зробити: cocoadventures.org/post/104137872754/…
Камчатка

8
Щоб людям було легше копіювати та вставляти: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Друкарські помилки тут вас вб'ють.
эталуз

3
layer.shadowOpacity value must be 0,5 not 0,5
Desert Rose

43

Ви можете спробувати це .... Ви можете грати зі значеннями. У shadowRadiusдиктує ступінь розмиття. shadowOffsetдиктує, куди йде тінь.

Швидкий 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

Приклад із поширенням

Приклад із поширенням

Щоб створити основну тінь

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

Основний приклад тіней у Swift 2.0

ВИХІД


19

Просте і чисте рішення за допомогою Interface Builder

Додайте файл під назвою UIView.swift у свій проект (або просто вставте його у будь-який файл):

import UIKit

@IBDesignable extension UIView {

    /* The color of the shadow. Defaults to opaque black. Colors created
    * from patterns are currently NOT supported. Animatable. */
    @IBInspectable var shadowColor: UIColor? {
        set {
            layer.shadowColor = newValue!.CGColor
        }
        get {
            if let color = layer.shadowColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }

    /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
    * [0,1] range will give undefined results. Animatable. */
    @IBInspectable var shadowOpacity: Float {
        set {
            layer.shadowOpacity = newValue
        }
        get {
            return layer.shadowOpacity
        }
    }

    /* The shadow offset. Defaults to (0, -3). Animatable. */
    @IBInspectable var shadowOffset: CGPoint {
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
    }

    /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
    @IBInspectable var shadowRadius: CGFloat {
        set {
            layer.shadowRadius = newValue
        }
        get {
            return layer.shadowRadius
        }
    }
}

Тоді це буде доступне в Інтерфейсі Builder для кожного перегляду на панелі утиліт> Інспектор атрибутів:

Панель комунальних послуг

Ви можете легко встановити тінь зараз.

Примітки:
- Тінь не з’явиться в IB, лише під час виконання.
- Як сказав Мазен Кассер

Тим, хто не зміг домогтися цього [...], переконайтеся, що кліп Subviews ( clipsToBounds) не ввімкнено


Це правильна відповідь - конфігурація коду для майбутніх інтерфейсів
Crake

Це рішення наштовхує мене на Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
неробочу

1
Мені довелося імпортувати, UIKitщоб він працював, базового Foundationімпорту, створеного XCode, недостатньо, але добре компілюється. Я повинен був скопіювати весь вихідний код. Дякую Акселю за це приємне рішення.
Xvolks

13

Я використовую це як частину своїх утиліт. Завдяки цьому ми можемо не лише встановити тінь, але й отримати закруглений кут для будь-якого UIView. Також ви можете встановити, яку кольорову тінь ви віддаєте перевагу. Зазвичай воліють чорний колір, але іноді, коли фон не білий, ви можете хотіти чогось іншого. Ось що я використовую -

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

Для цього нам потрібно зателефонувати так - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];


7

Швидкий 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}

якщо ви збираєтеся цей метод, я б розглядав можливість додавання параметрів, щоб ви могли відрегулювати значення так, щоб його динамічно
Josh O'Connor

Це не дає мені закругленого кута, чи я роблю щось не так?
Nikhil Manapure

@NikhilManapure нічого не відбувається, це може бути тому, що функція installShadow()не викликається з viewDidLoad()абоviewWillAppear()
neoneye

Я називав це через перевантажений drawметод зору. Тінь іде правильно, але закруглений кут - ні.
Нікхіл Манапуре

@NikhilManapure немає закруглених меж, це може бути тому, що вигляд є UIStackViewлише компонуванням і не має перегляду. Можливо, вам доведеться вставити звичайний UIViewяк контейнер для всього.
neoneye

6

Якщо ви хочете використовувати StoryBoard і не хочете продовжувати набирати атрибути часу виконання, ви можете легко створити розширення для представлень і зробити їх корисними у розгортці.

Крок 1. створити розширення

extension UIView {

@IBInspectable var shadowRadius: CGFloat {
    get {
        return layer.shadowRadius
    }
    set {
        layer.shadowRadius = newValue
    }
}

@IBInspectable var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

@IBInspectable var shadowOffset: CGSize {
    get {
        return layer.shadowOffset
    }
    set {
        layer.shadowOffset = newValue
    }
}

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

крок 2. тепер ви можете використовувати ці атрибути в раскадровцізображення розповіді


3

Тим, хто не зміг змусити це працювати (як я!), Спробувавши всі відповіді тут, просто переконайтесь, що кліп Subviews не ввімкнено інспектором атрибутів ...


1

Швидкий 3

self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5

1

Ви можете використовувати мою корисну функцію, створену для тіні та кута радіуса, як показано нижче:

- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{

    // drop shadow
    [view.layer setShadowRadius:shadowRadius];
    [view.layer setShadowOpacity:shadowOpacity];
    [view.layer setShadowOffset:shadowOffset];
    [view.layer setShadowColor:shadowColor.CGColor];

    // border radius
    [view.layer setCornerRadius:cornerRadius];

    // border
    [view.layer setBorderColor:borderColor.CGColor];
    [view.layer setBorderWidth:borderWidth];
}

Сподіваюся, це допоможе вам !!!


1

Всі відповідь добре, але я хочу додати ще один момент

Якщо ви зіткнулися з проблемою, коли у вас є комірки таблиці, видаліть нову клітинку, є невідповідність тіні, тому в цьому випадку вам потрібно помістити свій тіньовий код у метод layoutSubviews, щоб він міг добре поводитися в будь-яких умовах.

-(void)layoutSubviews{
    [super layoutSubviews];

    [self.contentView setNeedsLayout];
    [self.contentView layoutIfNeeded];
    [VPShadow applyShadowView:self];
}

або у ViewControllers для певного тіньового коду подання перегляду в наступному методі, щоб він працював добре

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

    [self.viewShadow layoutIfNeeded];
    [VPShadow applyShadowView:self.viewShadow];
}

Я змінив свою тіньову реалізацію для нових розробників для більш узагальненої форми, наприклад:

/*!
 @brief Add shadow to a view.

 @param layer CALayer of the view.

 */
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
    layer.masksToBounds = NO;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
    layer.shadowOpacity = alpha;
    layer.shadowRadius = radius;// blur effect
    layer.shadowPath = shadowPath.CGPath;
}

1

Для співмешканців Xamarians версія відповіді Xamarin.iOS / C # виглядатиме так:

public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
    CGContext currentContext = UIGraphics.GetCurrentContext();
    currentContext.SaveState();
    currentContext.SetShadow(new CGSize(-15, 20), 5);
    base.DrawRect(area, formatter);
    currentContext.RestoreState();                
}

Основна відмінність полягає в тому, що ви набуваєте екземпляр, CGContextна якому ви безпосередньо викликаєте відповідні методи.


1

Ви можете використовувати це, Extensionщоб додати тінь

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
    {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

ви можете назвати це як

your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)

0

Ескіз тіні за допомогою IBDesignable та IBInspectable у Swift 4

ЯК ВИКОРИСТОВАТИ ЇЇ

DEMO

ЕЛЕКТРИЧНИЙ СКЕТ І ХЕКДУ БІЛЬШЕ

Тіньовий приклад

КОД

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

    @IBInspectable var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

ВИХІД

DEMO вихід


Ви можете вдосконалити код, видаливши з атрибутів тіньові префікси. Мета ShadowView чітка щодо роботи в тіні. Ще одне - ви можете додати до цього класу радіус кута. (Сьогодні більшість тіньових поглядів передбачає радіус кута)
математика
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.