Как нарисовать тень под 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+, поэтому, если ваше приложение должно работать на старой версии, вы должны использовать решение Christian или статическое изображение позади представления, если это вариант.
florianbuerger
119
Это решение также требует добавления #import <QuartzCore/QuartzCore.h>"в .h файл.
MusiGenesis
26
Установка masksToBoundsдля NOсведет на нет cornerRadius, нет?
pixelfreak
4
Хорошо, чтобы решить это, backgroundColor должен быть установлен на слое, и представление должно быть прозрачным.
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;
ZYiOS
источник
1
Вероятно, стоит отметить, что эта оптимизация полезна только в том случае, если ваш вид визуально прямоугольный.
Бенджамин Добелл
1
self.layer.shadowPath ... вместо чего? или просто добавив к нему
Chrizzz
2
Просто добавьте эту дополнительную строку в дополнение к другим.
Кристоферкоттон
11
@NathanGaskin - рисование теней является дорогостоящей операцией, поэтому, например, если ваше приложение допускает другую ориентацию интерфейса, и вы начинаете поворачивать устройство, не указывая явно путь тени, оно должно быть визуализировано несколько раз во время этой анимации, что, в зависимости от форма, вероятно, заметно замедляет анимацию
Питер Пайчл
9
@BenjaminDobell Эта конкретная линия работает только для прямоугольников, но вы также можете создавать непрямоугольные пути. Например, если у вас есть скругленный прямоугольник, вы можете использовать bezierPathWithRoundedRect: cornerRadius:
Дэн Дайер
160

То же решение, но только для напоминания: вы можете определить тень прямо в раскадровке.

Пример:

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

Antzi
источник
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 должно быть 0,5, а не 0,5
Роза пустыни
43

Вы можете попробовать это .... вы можете играть со значениями. В shadowRadiusдиктует степень размытия. shadowOffsetдиктует куда идет тень

Swift 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

ВЫВОД

О-MKAR
источник
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
        }
    }
}

Тогда это будет доступно в Интерфейсном Разработчике для каждого представления на Панели утилит> Инспектор атрибутов:

Панель утилит

Вы можете легко установить тень сейчас.

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

Тем, кто не смог заставить это работать [...], убедитесь, что Clip 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.
Xvolks
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
    }
}
neoneye
источник
если бы вы использовали этот метод, я бы рассмотрел добавление параметров, чтобы вы могли настроить значения так, чтобы они были динамичными
Джош О'Коннор
Это не дает мне закругленный угол, я делаю что-то не так?
Нихил Манапур
@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. теперь вы можете использовать эти атрибуты в раскадровкеизображение раскадровки

king_T
источник
3

Тем, кто не смог заставить это работать (как я!), Попробовав все ответы здесь, просто убедитесь, что Clip 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];
}

Я изменил свою теневую реализацию для новых разработчиков для более обобщенной формы ex:

/*!
 @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;
}
Vishal16
источник
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)
pigeon_39
источник
0

Эскиз тени с использованием IBDesignable и IBInspectable в Swift 4

КАК ЭТО ИСПОЛЬЗОВАТЬ

DEMO

ЭСКИЗ И XCODE СТОРОНА ПО СТОРОНЕ

Shadow Exampl

КОД

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

ВЫВОД

ДЕМО ВЫХОД

О-MKAR
источник
Вы можете улучшить код, удалив теневые префиксы из атрибутов. Цель ShadowView ясна в отношении теневой операции. Еще одна вещь, вы можете добавить угловой радиус для этого класса. (В настоящее время большинство теневых представлений включает в себя угловой радиус)
математика