UITextView 渐变层应用不起作用

UITextView gradient layer apply not working

我想在 UITextView 的顶部 10% 和底部 10% 上应用渐变图层。为此,我放置了一个名为容器视图的虚拟 UIView,并使 UITextView 成为它的子视图。然后我添加以下代码:

   if let containerView = textView.superview {
        let gradient = CAGradientLayer(layer: containerView.layer)
        gradient.frame = containerView.bounds
        gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor]
        gradient.locations = [0.0, 0.1, 0.9, 1.0]
        containerView.layer.mask = gradient
    } 

但是渐变只应用到顶部,而不是底部。代码有问题吗?

此外,如果我随时通过修改它的约束来调整容器视图的大小,我是否需要每次都编辑遮罩层?

编辑:这是@DonMag 回答的输出。

但我想要的是像这张图片那样的文字在底部淡化的东西。

编辑 2:

以下是 DonMag 修改答案后的截图。

编辑 - 澄清所需效果后...

关于为什么您只看到顶部看台上的渐变,我的初步回答是:

You're only seeing the gradient on the top because you gave it four locations but only two colors.

那么,既然您提供了您正在尝试做的事情的图像...

使用此 DoubleGradientMaskView 作为文本视图的“容器”视图:

class DoubleGradientMaskView: UIView {
    
    let gradientLayer = CAGradientLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        gradientLayer.colors = [UIColor.clear.cgColor, UIColor.black.cgColor, UIColor.black.cgColor, UIColor.clear.cgColor]
        gradientLayer.locations = [0.0, 0.1, 0.9, 1.0]
        layer.mask = gradientLayer
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        gradientLayer.frame = bounds
    }
    
}

示例控制器:

class GradientTextViewViewController: UIViewController {
    
    let textView = UITextView()
    let containerView = DoubleGradientMaskView()
    let bkgImageView = UIImageView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        [bkgImageView, textView, containerView].forEach {
            [=11=].translatesAutoresizingMaskIntoConstraints = false
        }
        
        bkgImageView.contentMode = .scaleAspectFill
        if let img = UIImage(named: "background") {
            bkgImageView.image = img
        } else {
            bkgImageView.backgroundColor = .blue
        }
        
        view.addSubview(bkgImageView)
        view.addSubview(containerView)
        containerView.addSubview(textView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // add an image view so we can see the white text
            bkgImageView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
            bkgImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
            bkgImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
            bkgImageView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),

            // constraint text view inside container
            textView.topAnchor.constraint(equalTo: containerView.topAnchor),
            textView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            textView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
            textView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            
            // constrain container Top / Bottom 40, Leading / Trailing 40
            containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),

        ])
        
        textView.isScrollEnabled = true
        
        textView.font = UIFont.systemFont(ofSize: 48.0, weight: .bold)
        textView.textColor = .white
        textView.backgroundColor = .clear

        textView.text = String((1...20).flatMap { "This is row \([=11=])\n" })
        
    }

}

结果:

或者,使用蓝色背景而不是图像:


你只在顶部看到渐变,因为你给了它四个个位置,但只有两种种颜色。

将颜色更改为:

gradient.colors = [UIColor.clear.cgColor, UIColor.black.cgColor, UIColor.black.cgColor, UIColor.clear.cgColor]

可能会给你想要的外观...但你需要额外的代码来处理尺寸变化。

如果您使用此 class 作为您的“容器”视图,将自动调整大小:

class DoubleGradientView: UIView {
    
    var gradientLayer: CAGradientLayer!
    
    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        gradientLayer = self.layer as? CAGradientLayer
        gradientLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor, UIColor.clear.cgColor, UIColor.black.cgColor]
        gradientLayer.locations = [0.0, 0.1, 0.9, 1.0]
    }
    
}

这是一个示例控制器。它在容器中创建了两个“文本视图”。

  • 最上面的是可滚动的,高度为100。
  • 底部的不可滚动,因此它会在您键入时调整其高度以适应文本。

两者都被限制在 60 点的前导/尾随,因此当您旋转设备时,您还会看到自动渐变更新。

class GradientBehindTextViewViewController: UIViewController {
    
    let textView1 = UITextView()
    let containerView1 = DoubleGradientView()

    let textView2 = UITextView()
    let containerView2 = DoubleGradientView()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        [textView1, containerView1, textView2, containerView2].forEach {
            [=14=].translatesAutoresizingMaskIntoConstraints = false
        }
        
        containerView1.addSubview(textView1)
        view.addSubview(containerView1)
        
        containerView2.addSubview(textView2)
        view.addSubview(containerView2)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constraint text view inside container
            textView1.topAnchor.constraint(equalTo: containerView1.topAnchor),
            textView1.leadingAnchor.constraint(equalTo: containerView1.leadingAnchor),
            textView1.trailingAnchor.constraint(equalTo: containerView1.trailingAnchor),
            textView1.bottomAnchor.constraint(equalTo: containerView1.bottomAnchor),

            // constrain container Top + 40, Leading / Trailing 80
            containerView1.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            containerView1.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 80.0),
            containerView1.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -80.0),

            // text view 1 will have scrolling enabled, so we'll set its height to 100
            containerView1.heightAnchor.constraint(equalToConstant: 100.0),
            
            // constraint text view inside container
            textView2.topAnchor.constraint(equalTo: containerView2.topAnchor),
            textView2.leadingAnchor.constraint(equalTo: containerView2.leadingAnchor),
            textView2.trailingAnchor.constraint(equalTo: containerView2.trailingAnchor),
            textView2.bottomAnchor.constraint(equalTo: containerView2.bottomAnchor),
            
            // constrain container2 Top to container1 bottom + 40, Leading / Trailing 80
            containerView2.topAnchor.constraint(equalTo: containerView1.bottomAnchor, constant: 40.0),
            containerView2.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 80.0),
            containerView2.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -80.0),
            
            // text view 2 will NOT scroll (it will size with the text) so no height / bottom

        ])
        
        // text view 1 should scroll
        textView1.isScrollEnabled = true
        
        // text view 1 should NOT scroll we want the text view to size itelf as we type
        textView2.isScrollEnabled = false
        
        // let the gradient show through
        textView1.backgroundColor = .clear
        textView2.backgroundColor = .clear

        textView1.text = "Initial text for text view 1."
        textView2.text = "Initial text for text view 2."

    }
    
}

@DongMag 解决方案很复杂。相反,您只需要一个像这样实现的掩码:

@IBDesignable
class MaskableLabel: UILabel {
    var maskImageView = UIImageView()

    @IBInspectable
    var maskImage: UIImage? {
        didSet {
            maskImageView.image = maskImage
            updateView()
        }
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateView()
    }

    func updateView() {
        if maskImageView.image != nil {
            maskImageView.frame = bounds
            mask = maskImageView
        }
    }
}

然后用一个简单的渐变遮罩like this,你甚至可以在故事板中看到它。

注意:您可以使用此方法并将UILabel替换为您想要子类化的任何其他视图。

这里是the example project on the GitHub