Какой лучший способ добавить тень к моему UIView

108

Я пытаюсь добавить тень к представлениям, которые наложены друг на друга, представления сворачиваются, позволяя видеть контент в других представлениях, в этом ключе я хочу оставить view.clipsToBoundsВКЛ, чтобы при сворачивании представлений их содержимое было обрезано.

Похоже, это затруднило мне добавление тени к слоям, поскольку, когда я включаю, clipsToBoundsтени также обрезаются.

Я пытался манипулировать view.frameи view.boundsдобавлять тень к кадру, но позволять границам быть достаточно большими, чтобы охватить его, однако мне не повезло с этим.

Вот код, который я использую для добавления тени (работает только при clipsToBoundsвыключенном состоянии, как показано).

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Вот скриншот, на котором тень применяется к самому светлому серому слою. Надеюсь, это дает представление о том, как мой контент будет перекрываться, если clipsToBoundsон выключен.

Теневое приложение.

Как я могу добавить тень к моему UIViewи сохранить мой контент обрезанным?

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

Мы з
источник

Ответы:

280

Попробуй это:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

Прежде всего : важно UIBezierPathиспользовать as shadowPath. Если вы его не используете, вы можете сначала не заметить разницы, но зоркий глаз заметит определенную задержку, возникающую во время таких событий, как вращение устройства и / или тому подобное. Это важная настройка производительности.

Что касается конкретно вашей проблемы : важная строка view.layer.masksToBounds = NO. Он отключает отсечение подслоев слоя представления, которые выходят за границы представления.

Для тех, кто задается вопросом, в чем разница между masksToBounds(на слое) и собственным clipToBoundsсвойством представления: на самом деле их нет. Переключение одного из них повлияет на другое. Просто другой уровень абстракции.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}
пклуз
источник
1
Спасибо, я попробовал ваш код, а затем попытался добавить masksToBounds = NO;к своему оригиналу - с обеими попытками, которые я оставил clipsToBounds = YES;включенными - обе не смогли обрезать контент. Херес ScreenCap того , что случилось с вашим Пример> youtu.be/tdpemc_Xdps
WEZ
1
Есть идеи, как заставить тень обнимать закругленный угол, а не рендерить, как если бы угол все еще был квадратом?
John Erck
7
@JohnErck попробовать это: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Не тестировался, но должен дать желаемый результат.
pkluz
1
Я узнал, что при анимации вида тень оставляет серые следы при движении. Удаление линий shadowPath решило эту проблему
ishahak
1
@shoe, потому что каждый раз, когда размер представления изменяется, layoutSubviews()вызывается. И если мы не компенсируем в этом месте, отрегулировав, shadowPathчтобы отразить текущий размер, у нас будет вид с измененным размером, но тень все равно будет иметь старый (начальный) размер и либо не будет отображаться, либо смотреть туда, где не должна. .
pkluz
65

Ответ Васабии в Swift 2.3:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

А в Swift 3/4/5:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

Поместите этот код в layoutSubviews (), если вы используете AutoLayout.

В SwiftUI все намного проще:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)
Барт ван Куик
источник
11
Спасибо за вниманиеlayoutSubviews
Islam Q.
1
или даже лучше в viewDidLayoutSubviews ()
Макс Маклауд
1
предпочитают быстрые инициализаторы структур вместо вариантов создания, то естьCGSize(width: 0, height: 0.5)
Оливер Аткинсон
Я добавляю этот код в layoutSubviews (), он все еще не отображался в IB. Есть ли другие настройки, которые я должен выбрать?
осел
Спасибо. Я использовал Auto Layout и подумал про себя - ну ... view.boundsон не был создан настолько очевидно, что shadowPathне может ссылаться на прямоугольник представления, а на некоторые CGRectZero. В любом случае, это layoutSubviewsрешило мою проблему!
devdc
13

Хитрость заключается в masksToBoundsправильном определении свойства слоя вашего представления:

view.layer.masksToBounds = NO;

и он должен работать.

( Источник )

Серджио
источник
13

Вы можете создать расширение для UIView для доступа к этим значениям в редакторе дизайна.

Параметры тени в редакторе дизайна

extension UIView{

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

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

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

    @IBInspectable var shadowOpacity: Float{
        get{
            return self.layer.shadowOpacity
        }
        set{
            self.layer.shadowOpacity = newValue
        }
    }
}
Крис Стиллвелл
источник
как ссылаться на это расширение в построителе интерфейса
Amr Angry
IBInspectable делает его доступным в конструкторе интерфейсов
Нитеш
Могу ли я добиться этого и в Objective C?
Хариш Патхак
2
если вы хотите иметь предварительный просмотр в реальном времени (в конструкторе интерфейсов), здесь вы можете найти хорошую статью об этом spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill
7

Вы также можете установить тень на свой вид из раскадровки

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

Pallavi
источник
2

На viewWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

Использование расширения UIView:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

Использование:

sampleView.addDropShadowToView(sampleView)
AG
источник
1
Просто усилите targetView: UIViewи удалите челку.
bluebamboo 05
6
Почему бы не использовать self? Мне кажется намного удобнее.
LinusGeffarth
3
Лучший способ сделать это - удалить targetView в качестве параметра и использовать self вместо targetView. Это устраняет избыточность и позволяет называть это так: sampleView.addDropShadowToView ()
maxcodes
Комментарий Макса Нельсона великолепен. Мое предложение - использовать if letсинтаксис вместо!
Джонни
0

Итак, да, вы должны предпочесть свойство shadowPath для повышения производительности, но также: Из файла заголовка CALayer.shadowPath

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

Менее известный трюк - использовать одну и ту же ссылку на нескольких уровнях. Конечно, они должны использовать одну и ту же форму, но это обычное дело для ячеек представления таблицы / коллекции.

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

Руфус Молл
источник