Добавление внутренней тени в верхнюю часть UIView

Я посмотрел вверх, но не смог найти, как добавить внутреннюю тень в UIView, только сверху (сверху вниз) для Swift. Как лучше всего добавить внутренний круг в Swift?

Изменить: я нашел несколько вопросов и ответов на SO, однако они либо в obj-c, либо выглядят такими сложными. Я просто искал более быстрый способ, если он есть

Чего я хочу достичь:

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


person senty    schedule 07.06.2016    source источник
comment
github.com/inamiy/YIInnerShadowView   -  person Arbitur    schedule 07.06.2016
comment
@Arbitur, ваша ссылка в Objective-C, которую я не смог перевести. В своем SO-ответе он выглядит довольно сложным фрагментом кода и всеми его сторонами. Он также не использует что-то простое, например CGSizeMake, поэтому я хотел спросить об этом. Я просто искал более быстрый способ   -  person senty    schedule 07.06.2016
comment
Если вы хотите убедиться, что тень остается только вдоль верхней границы, независимо от радиуса размытия/смещения/и т. д. и не перетекает в другие границы, лучше всего добавить UIImageView в качестве дочернего представления, приклеенного к верхней границе и растянутого по всей ширине. Если вам нужна специальная обработка боковых краев (как, кажется, предполагает ваш скриншот), возможно, растягиваемое изображение со вставками поможет.   -  person Nicolas Miari    schedule 07.06.2016
comment
Хотя, пожалуй, разумнее было бы переопределить drawRect(_:) и сделать все с Core Graphics. Никаких посторонних дополнительных представлений, никаких графических ресурсов, но более продвинутый код.   -  person Nicolas Miari    schedule 07.06.2016
comment
@NicolasMiari Я думаю, что это был бы хороший обходной путь, но не тогда ответ. Спасибо; и хотелось бы узнать ответ :)   -  person senty    schedule 07.06.2016


Ответы (6)


Вот чистая версия Swift, которую я сделал на скорую руку:

public class EdgeShadowLayer: CAGradientLayer {

    public enum Edge {
        case Top
        case Left
        case Bottom
        case Right
    }

    public init(forView view: UIView,
                edge: Edge = Edge.Top,
                shadowRadius radius: CGFloat = 20.0,
                toColor: UIColor = UIColor.white,
                fromColor: UIColor = UIColor.black) {
        super.init()
        self.colors = [fromColor.cgColor, toColor.cgColor]
        self.shadowRadius = radius

        let viewFrame = view.frame

        switch edge {
            case .Top:
                startPoint = CGPoint(x: 0.5, y: 0.0)
                endPoint = CGPoint(x: 0.5, y: 1.0)
                self.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: shadowRadius)
            case .Bottom:
                startPoint = CGPoint(x: 0.5, y: 1.0)
                endPoint = CGPoint(x: 0.5, y: 0.0)
                self.frame = CGRect(x: 0.0, y: viewFrame.height - shadowRadius, width: viewFrame.width, height: shadowRadius)
            case .Left:
                startPoint = CGPoint(x: 0.0, y: 0.5)
                endPoint = CGPoint(x: 1.0, y: 0.5)
                self.frame = CGRect(x: 0.0, y: 0.0, width: shadowRadius, height: viewFrame.height)
            case .Right:
                startPoint = CGPoint(x: 1.0, y: 0.5)
                endPoint = CGPoint(x: 0.0, y: 0.5)
                self.frame = CGRect(x: viewFrame.width - shadowRadius, y: 0.0, width: shadowRadius, height: viewFrame.height)
        }
    }

    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }   
}

Чтобы использовать его,

let topShadow = EdgeShadowLayer(forView: targetView, edge: .Top)
targetView.layer.addSublayer(topShadow)

Обратите внимание, что по умолчанию используется черно-белый градиент глубиной 20 точек.

Полный код с образцом UIViewController, позволяющим переключать тени во всех четырех углах изображения, можно найти по адресу https://github.com/jrtibbetts/Tenebrae. Я также довольно тщательно задокументировал EdgeShadowLayer.

person NRitH    schedule 07.06.2016
comment
Что такое targetView и что такое view здесь? Разве оба не одинаковы? - person Nagendra Rao; 15.03.2018
comment
Обновил ответ. - person Nagendra Rao; 16.03.2018
comment
Не могли бы вы использовать UIColor.clear вместо toColor, чтобы второй цвет был прозрачным? Таким образом, он может быть независим от цвета основного представления. - person Lou Valencia; 04.04.2019
comment
Конечно; Я не понимаю, почему бы и нет. - person NRitH; 04.04.2019
comment
Я реализовал это. Смотрите мой пост ниже для кода и скриншотов. - person Lou Valencia; 04.04.2019

Я использовал внутреннюю тень для UIView, используя Objective-C. Я пытаюсь перевести код в swift. Пожалуйста, простите меня за мой плохой быстрый синтаксис

вы можете вызвать функцию ниже в UIView.didMoveToSuperview

func drawShadow() {
    if nil == self.shadowLayer {
        let size = self.frame.size
        self.clipsToBounds = true
        let layer: CALayer = CALayer()
        layer.backgroundColor = UIColor.lightGrayColor().CGColor
        layer.position = CGPointMake(size.width / 2, -size.height / 2 + 0.5)
        layer.bounds = CGRectMake(0, 0, size.width, size.height)
        layer.shadowColor = UIColor.darkGrayColor().CGColor
        layer.shadowOffset = CGSizeMake(0.5, 0.5)
        layer.shadowOpacity = 0.8
        layer.shadowRadius = 5.0
        self.shadowLayer = layer

        self.layer.addSublayer(layer)
    }
}
person J.Hunter    schedule 07.06.2016
comment
Пока лучший ответ. ПО МОЕМУ МНЕНИЮ. В других ответах используются градиенты, а не то, как тени рисуются в Core Graphics. - person beyowulf; 07.06.2016
comment
Откуда self.shadowLayer? - person Jon Vogel; 19.12.2017
comment
@JonVogel Вероятно, это просто свойство CALayer, чтобы отслеживать, добавлена ​​ли тень еще или нет, а также для доступа и редактирования ее позже в жизненном цикле представления. Удаление проверки и присвоение ей — это нормально, если все, что вы хотите сделать, это добавить тень один раз. - person kevin; 23.05.2018
comment
self.shadowLayer, кажется, отсутствует. Можете ли вы объяснить, откуда он исходит? - person Jayprakash Dubey; 23.11.2018
comment
@Jayprakash Dubey Просто свойство, такое как private var shadowLayer: CALayer? - person J.Hunter; 23.11.2018

Я подправил модификацию, сделанную @anoop4real, используя clear как toColor, и сделал интерфейс более соответствующим настройкам теней в CALayer, включая значения по умолчанию, за исключением непрозрачности, которая по умолчанию установлена ​​​​на 0,0. Я выбрал значение по умолчанию 0,6, так как оно выглядело наиболее естественным.

extension UIView {
    func addShadow(to edges: [UIRectEdge], radius: CGFloat = 3.0, opacity: Float = 0.6, color: CGColor = UIColor.black.cgColor) {

        let fromColor = color
        let toColor = UIColor.clear.cgColor
        let viewFrame = self.frame
        for edge in edges {
            let gradientLayer = CAGradientLayer()
            gradientLayer.colors = [fromColor, toColor]
            gradientLayer.opacity = opacity

            switch edge {
            case .top:
                gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
                gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
                gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: radius)
            case .bottom:
                gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
                gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
                gradientLayer.frame = CGRect(x: 0.0, y: viewFrame.height - radius, width: viewFrame.width, height: radius)
            case .left:
                gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
                gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
                gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: radius, height: viewFrame.height)
            case .right:
                gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
                gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
                gradientLayer.frame = CGRect(x: viewFrame.width - radius, y: 0.0, width: radius, height: viewFrame.height)
            default:
                break
            }
            self.layer.addSublayer(gradientLayer)
        }
    }

    func removeAllShadows() {
        if let sublayers = self.layer.sublayers, !sublayers.isEmpty {
            for sublayer in sublayers {
                sublayer.removeFromSuperlayer()
            }
        }
    }
}

Верхний вид — это настройки по умолчанию, а нижний использует радиус 5,0, чтобы показать более четко.

view1.addShadow([.top, .bottom, .left, .right])
view2.addShadow([.top, .bottom, .left, .right], radius: 5.0)
view2.backgroundColor = .orange

виды с внутренней тенью

person Lou Valencia    schedule 04.04.2019
comment
Ваш removeAllShadows вообще удаляет все подслои UIView! Для правильного использования категории вы должны добавить связанный объект в addShadow и удалить их в removeShadows или просто удалить этот метод удаления из своей реализации. - person iago849; 14.08.2019
comment
Это создает мультипликативный эффект, когда тени накладываются друг на друга. то есть углы становятся темнее. - person Womble; 07.10.2020
comment
Снизу и справа отображаются только при касании ячейки в UIcollectionviewcell - person AmitTank; 24.01.2021

Я обновил ответ @NRitH и сделал из него расширение, также измененное, чтобы вы могли манипулировать несколькими ребрами за один раз.

Применение

myview.addShadow(to: [.top,.bottom], radius: 15.0)

extension UIView{

    func addShadow(to edges:[UIRectEdge], radius:CGFloat){

        let toColor = UIColor(colorLiteralRed: 235.0/255.0, green: 235.0/255.0, blue: 235.0/255.0, alpha: 1.0)
        let fromColor = UIColor(colorLiteralRed: 188.0/255.0, green: 188.0/255.0, blue: 188.0/255.0, alpha: 1.0)
        // Set up its frame.
        let viewFrame = self.frame
        for edge in edges{
            let gradientlayer          = CAGradientLayer()
            gradientlayer.colors       = [fromColor.cgColor,toColor.cgColor]
            gradientlayer.shadowRadius = radius

            switch edge {
            case UIRectEdge.top:
                gradientlayer.startPoint = CGPoint(x: 0.5, y: 0.0)
                gradientlayer.endPoint = CGPoint(x: 0.5, y: 1.0)
                gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: gradientlayer.shadowRadius)
            case UIRectEdge.bottom:
                gradientlayer.startPoint = CGPoint(x: 0.5, y: 1.0)
                gradientlayer.endPoint = CGPoint(x: 0.5, y: 0.0)
                gradientlayer.frame = CGRect(x: 0.0, y: viewFrame.height - gradientlayer.shadowRadius, width: viewFrame.width, height: gradientlayer.shadowRadius)
            case UIRectEdge.left:
                gradientlayer.startPoint = CGPoint(x: 0.0, y: 0.5)
                gradientlayer.endPoint = CGPoint(x: 1.0, y: 0.5)
                gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
            case UIRectEdge.right:
                gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.5)
                gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.5)
                gradientlayer.frame = CGRect(x: viewFrame.width - gradientlayer.shadowRadius, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
            default:
                break
            }
            self.layer.addSublayer(gradientlayer)
        }

    }

    func removeAllSublayers(){
        if let sublayers = self.layer.sublayers, !sublayers.isEmpty{
            for sublayer in sublayers{
                sublayer.removeFromSuperlayer()
            }
        }
    }

}

Тень

person anoop4real    schedule 14.11.2017

Я переписал решение @NRitH на Swift 3, а также немного отрефакторил его:

final class SideShadowLayer: CAGradientLayer {
    enum Side {
        case top,
        bottom,
        left,
        right
    }

    init(frame: CGRect, side: Side, shadowWidth: CGFloat,
         fromColor: UIColor = .black,
         toColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0),
         opacity: Float = 0.5) {
        super.init()

        colors = [fromColor.cgColor, toColor.cgColor]
        self.opacity = opacity

        switch side {
        case .bottom:
            startPoint = CGPoint(x: 0.5, y: 1.0)
            endPoint = CGPoint(x: 0.5, y: 0.0)
            self.frame = CGRect(x: 0, y: frame.height - shadowWidth, width: frame.width, height: shadowWidth)

        case .top:
            startPoint = CGPoint(x: 0.5, y: 0.0)
            endPoint = CGPoint(x: 0.5, y: 1.0)
            self.frame = CGRect(x: 0, y: 0, width: frame.width, height: shadowWidth)

        case .left:
            startPoint = CGPoint(x: 0.0, y: 0.5)
            endPoint = CGPoint(x: 1.0, y: 0.5)
            self.frame = CGRect(x: 0, y: 0, width: shadowWidth, height: frame.height)

        case .right:
            startPoint = CGPoint(x: 1.0, y: 0.5)
            endPoint = CGPoint(x: 0.0, y: 0.5)
            self.frame = CGRect(x: frame.width - shadowWidth, y: 0, width: shadowWidth, height: frame.height)
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}
person Bohdan Savych    schedule 07.06.2017

Если вы не возражаете против использования clipsToBounds = true, вы можете создать новое смещение CALayer рядом с краем вашего вида и добавить тень к ЭТОМУ виду. Это то, что делает ответ Дж. Хантера.

Код J.Hunter добавляет верхнюю тень, здесь я обновил ее до Swift 5 и добавил внизу.

Свифт 5:

override func draw(_ rect: CGRect) {
  // Create Inner Shadow. Not sure about efficiency of this.
  // You may want to create a shadowLayer property
  //  and only run this code if it hasn't been created yet.
  let size = rect.size
  clipsToBounds = true // Don't want to see your fake view layer
  let innerShadowLayer: CALayer = CALayer()
  
  // Need to set a backgroundColor or it doesn't work
  innerShadowLayer.backgroundColor = UIColor.black.cgColor
  
  // Position your shadow layer (anchor point is in the center)
  //  on the edge of where your shadow needs to be.
  // In my case this moves the shadow layer to the
  //  bottom edge of my view
  innerShadowLayer.position = CGPoint(x: size.width / 2, y: size.height + (size.height / 2))
  
  // This could be smaller I think, just copying J.Hunter's code...
  innerShadowLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
  
  // Normal shadow layer properties you'd use for an outer shadow
  innerShadowLayer.shadowColor = UIColor.black.cgColor
  innerShadowLayer.shadowOffset = CGSize(width: 0, height: 0)
  innerShadowLayer.shadowOpacity = 0.3
  innerShadowLayer.shadowRadius = 3
  
  layer.addSublayer(innerShadowLayer)
}
person teradyl    schedule 04.06.2021