[英]UIView with shadow, rounded corners and custom drawRect
我必須創建一個具有圓角、邊框、陰影的自定義UIView
,並且它的drawRect()
方法被覆蓋以提供自定義繪圖代碼,通過這些代碼將幾條直線繪制到視圖中(我需要使用快速、輕量級的方法在這里,因為許多這些視圖可能會被渲染)。
我目前面臨的問題是,一旦我在視圖類中覆蓋drawRect()
,陰影就不再適用於圓角(即使其中還沒有任何自定義代碼)。 區別見附圖:
在視圖控制器中,我使用以下代碼:
view.layer.cornerRadius = 10;
view.layer.masksToBounds = true;
view.layer.borderColor = UIColor.grayColor().CGColor;
view.layer.borderWidth = 0.5;
view.layer.contentsScale = UIScreen.mainScreen().scale;
view.layer.shadowColor = UIColor.blackColor().CGColor;
view.layer.shadowOffset = CGSizeZero;
view.layer.shadowRadius = 5.0;
view.layer.shadowOpacity = 0.5;
view.layer.masksToBounds = false;
view.clipsToBounds = false;
在覆蓋的drawContext()
我會使用類似的東西:
var context:CGContext = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
CGContextMoveToPoint(context, 0.0, 0.0); //start at this point
CGContextAddLineToPoint(context, 20.0, 20.0); //draw to this point
CGContextStrokePath(context);
但是如上所述,即使不添加此代碼,也會出現陰影問題。
除了與圓角和陰影兼容的這種方法之外,還有其他/更好的方法可以將輕量級元素繪制到視圖上嗎? 我不想在視圖中添加任何不必要的額外視圖或圖像上下文,因為它們需要輕量級和高性能。
這是一個棘手的問題。 UIView
的clipsToBounds
是獲得圓角所必需的。 但是CALayer
的masksToBounds
必須是false
所以陰影是可見的。 不知何故,如果drawRect
沒有被覆蓋,則一切正常,但實際上它不應該被覆蓋。
解決方案是創建一個超級視圖來提供陰影(在下面的演示中,這是shadowView
)。 您可以在 Playground 中測試以下內容:
class MyView : UIView {
override func drawRect(rect: CGRect) {
let c = UIGraphicsGetCurrentContext()
CGContextAddRect(c, CGRectMake(10, 10, 80, 80))
CGContextSetStrokeColorWithColor(c , UIColor.redColor().CGColor)
CGContextStrokePath(c)
}
}
let superview = UIView(frame: CGRectMake(0, 0, 200, 200))
let shadowView = UIView(frame: CGRectMake(50, 50, 100, 100))
shadowView.layer.shadowColor = UIColor.blackColor().CGColor
shadowView.layer.shadowOffset = CGSizeZero
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowRadius = 5
let view = MyView(frame: shadowView.bounds)
view.backgroundColor = UIColor.whiteColor()
view.layer.cornerRadius = 10.0
view.layer.borderColor = UIColor.grayColor().CGColor
view.layer.borderWidth = 0.5
view.clipsToBounds = true
shadowView.addSubview(view)
superview.addSubview(shadowView)
結果:
我為 UIView 編寫了一個小的擴展來管理圓角和陰影。 由於變量是@IBInspectable,所以一切都可以直接在故事板中設置!
//
// UIView extensions.swift
//
// Created by Frédéric ADDA on 25/07/2016.
// Copyright © 2016 Frédéric ADDA. All rights reserved.
//
import UIKit
extension UIView {
@IBInspectable var shadow: Bool {
get {
return layer.shadowOpacity > 0.0
}
set {
if newValue == true {
self.addShadow()
}
}
}
@IBInspectable var cornerRadius: CGFloat {
get {
return self.layer.cornerRadius
}
set {
self.layer.cornerRadius = newValue
// Don't touch the masksToBound property if a shadow is needed in addition to the cornerRadius
if shadow == false {
self.layer.masksToBounds = true
}
}
}
func addShadow(shadowColor: CGColor = UIColor.black.cgColor,
shadowOffset: CGSize = CGSize(width: 1.0, height: 2.0),
shadowOpacity: Float = 0.4,
shadowRadius: CGFloat = 3.0) {
layer.shadowColor = shadowColor
layer.shadowOffset = shadowOffset
layer.shadowOpacity = shadowOpacity
layer.shadowRadius = shadowRadius
}
}
有一個要求:不要觸摸視圖上的 clipToBounds(在代碼中或在 IB 中)或圖層上的 maskToBound。
注意:一種不起作用的情況:tableViews。 由於 UITableView 在clipToBounds
自動觸發clipToBounds
,我們不能有陰影。
編輯:正如 Claudia Fitero 恰當地注意到的那樣,您需要在要添加陰影的視圖周圍留下一個小填充,否則陰影將不可見。 通常 2px-padding 就足夠了(取決於您的陰影半徑)。
陰影從視圖層內的任何內容中刪除。 當您禁用剪輯時,整個圖層矩形將填充為默認backgroundColor
因此陰影也變為矩形。 而不是用圓形蒙版剪裁它只是讓圖層的內容四舍五入,自己繪制它們。 而且layer
的邊框是圍繞它的邊界繪制的,所以你也需要自己繪制它。
例如,在backgroundColor
setter 中將實際背景顏色設置為clearColor
並使用drawRect
傳遞的顏色來繪制圓角矩形。
在下面的示例中,我將屬性聲明為IBInspectable
並將整個類聲明為IBDesignable
,因此所有內容都可以在故事板中設置。 這樣你甚至可以使用默認的背景選擇器來改變你的圓形矩形顏色。
@IBDesignable class RoundRectView: UIView {
@IBInspectable var cornerRadius: CGFloat = 0.0
@IBInspectable var borderColor: UIColor = UIColor.blackColor()
@IBInspectable var borderWidth: CGFloat = 0.5
private var customBackgroundColor = UIColor.whiteColor()
override var backgroundColor: UIColor?{
didSet {
customBackgroundColor = backgroundColor!
super.backgroundColor = UIColor.clearColor()
}
}
func setup() {
layer.shadowColor = UIColor.blackColor().CGColor;
layer.shadowOffset = CGSizeZero;
layer.shadowRadius = 5.0;
layer.shadowOpacity = 0.5;
super.backgroundColor = UIColor.clearColor()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override func drawRect(rect: CGRect) {
customBackgroundColor.setFill()
UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()
let borderRect = CGRectInset(bounds, borderWidth/2, borderWidth/2)
let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
borderColor.setStroke()
borderPath.lineWidth = borderWidth
borderPath.stroke()
// whatever else you need drawn
}
}
@IBDesignable class RoundedView: UIView {
@IBInspectable var cornerRadius: CGFloat = 0.0
@IBInspectable var borderColor: UIColor = UIColor.black
@IBInspectable var borderWidth: CGFloat = 0.5
private var customBackgroundColor = UIColor.white
override var backgroundColor: UIColor?{
didSet {
customBackgroundColor = backgroundColor!
super.backgroundColor = UIColor.clear
}
}
func setup() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5.0
layer.shadowOpacity = 0.5
super.backgroundColor = UIColor.clear
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override func draw(_ rect: CGRect) {
customBackgroundColor.setFill()
UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()
let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2)
let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
borderColor.setStroke()
borderPath.lineWidth = borderWidth
borderPath.stroke()
// whatever else you need drawn
}
}
IB_DESIGNABLE
@interface RoundRectView : UIView
@property IBInspectable CGFloat cornerRadius;
@property IBInspectable UIColor *borderColor;
@property IBInspectable CGFloat borderWidth;
@end
@interface RoundRectView()
@property UIColor *customBackgroundColor;
@end
@implementation RoundRectView
-(void)setup{
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowRadius = 5.0;
self.layer.shadowOpacity = 0.5;
[super setBackgroundColor:[UIColor clearColor]];
}
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self setup];
}
return self;
}
-(void)setBackgroundColor:(UIColor *)backgroundColor{
self.customBackgroundColor = backgroundColor;
super.backgroundColor = [UIColor clearColor];
}
-(void)drawRect:(CGRect)rect{
[self.customBackgroundColor setFill];
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.cornerRadius] fill];
CGFloat borderInset = self.borderWidth/2;
CGRect borderRect = CGRectInset(self.bounds, borderInset, borderInset);
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:borderRect cornerRadius:self.cornerRadius - borderInset];
[self.borderColor setStroke];
borderPath.lineWidth = self.borderWidth;
[borderPath stroke];
// whatever else you need drawn
}
@end
這是Hodit答案的 swift3 版本,我不得不使用它並在此處找到它,並對 XCode 8 進行了一般更正。就像魅力一樣!
@IBDesignable class RoundRectView: UIView {
@IBInspectable var cornerRadius: CGFloat = 0.0
@IBInspectable var borderColor: UIColor = UIColor.black
@IBInspectable var borderWidth: CGFloat = 0.5
private var customBackgroundColor = UIColor.white
override var backgroundColor: UIColor?{
didSet {
customBackgroundColor = backgroundColor!
super.backgroundColor = UIColor.clear
}
}
func setup() {
layer.shadowColor = UIColor.black.cgColor;
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5.0;
layer.shadowOpacity = 0.5;
super.backgroundColor = UIColor.clear
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setup()
}
override func draw(_ rect: CGRect) {
customBackgroundColor.setFill()
UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()
let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2)
let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
borderColor.setStroke()
borderPath.lineWidth = borderWidth
borderPath.stroke()
// whatever else you need drawn
}
}
SWIFT 3 解決方案
改編自 Mundi 的回答
class MyView : UIView {
override func draw(_ rect: CGRect) {
let c = UIGraphicsGetCurrentContext()
c!.addRect(CGRect(x: 10, y: 10, width: 80, height: 80))
c!.setStrokeColor(UIColor.red.cgColor)
c!.strokePath()
}
}
let superview = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
let shadowView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize.zero
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowRadius = 5
let view = MyView(frame: shadowView.bounds)
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 10.0
view.layer.borderColor = UIColor.gray.cgColor
view.layer.borderWidth = 0.5
view.clipsToBounds = true
shadowView.addSubview(view)
superview.addSubview(shadowView)
斯威夫特 3
我做了一個 UIView 擴展,它與 Mundi 建議的想法基本相同:
extension UIView {
func addShadowView() {
//Remove previous shadow views
superview?.viewWithTag(119900)?.removeFromSuperview()
//Create new shadow view with frame
let shadowView = UIView(frame: frame)
shadowView.tag = 119900
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 2, height: 3)
shadowView.layer.masksToBounds = false
shadowView.layer.shadowOpacity = 0.3
shadowView.layer.shadowRadius = 3
shadowView.layer.shadowPath = UIBezierPath(rect: bounds).cgPath
shadowView.layer.rasterizationScale = UIScreen.main.scale
shadowView.layer.shouldRasterize = true
superview?.insertSubview(shadowView, belowSubview: self)
}}
用:
class MyCVCell: UICollectionViewCell {
@IBOutlet weak var containerView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
containerView.addShadowView()
}}
在 Swift 4.1 中。 為了制作 UIView 的圓角,我創建了 UIView 的擴展如下。
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var viewOuter: UIView!
@IBOutlet weak var viewInner: UIView!
override func viewDidLoad() {
super.viewDidLoad()
viewOuter.backgroundColor = UIColor.clear
viewInner.roundCorners(15.0)
viewOuter.addViewShadow()
}
}
extension UIView {
public func roundCorners(_ cornerRadius: CGFloat) {
self.layer.cornerRadius = cornerRadius
self.clipsToBounds = true
self.layer.masksToBounds = true
}
public func addViewShadow() {
DispatchQueue.main.asyncAfter(deadline: (.now() + 0.2)) {
let shadowLayer = CAShapeLayer()
shadowLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 15).cgPath
shadowLayer.fillColor = UIColor.white.cgColor
shadowLayer.shadowColor = UIColor.lightGray.cgColor
shadowLayer.shadowPath = shadowLayer.path
shadowLayer.shadowOffset = CGSize(width: 2.6, height: 2.6)
shadowLayer.shadowOpacity = 0.8
shadowLayer.shadowRadius = 8.0
self.layer.insertSublayer(shadowLayer, at: 0)
}
}
}
解決方案似乎比問題所暗示的要容易得多。 我有我的一個觀點,並使用@Hodit 答案的核心部分來讓它工作。 這就是你真正需要的:
- (void) drawRect:(CGRect)rect {
// make sure the background is set to a transparent color using IB or code
// e.g.: self.backgroundColor = [UIColor clearColor];
// draw a rounded rect in the view
[[UIColor whiteColor] setFill];
[[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:5.0] fill];
// apply shadow if you haven't already
self.layer.masksToBounds = NO;
self.layer.shadowColor = [[UIColor blackColor] CGColor];
self.layer.shadowOffset = CGSizeMake(0.0,3.0);
self.layer.shadowRadius= 1.0;
self.layer.shadowOpacity = 0.1;
// more code here
}
請注意,這不會剪輯子視圖。 視圖中位於 0,0 處的任何內容都將與可見的左上角圓角重疊。
我將此擴展用於UIView
:
Import UIKit
extension UIView {
/// A property that accesses the backing layer's opacity.
@IBInspectable
open var opacity: Float {
get {
return layer.opacity
}
set(value) {
layer.opacity = value
}
}
/// A property that accesses the backing layer's shadow
@IBInspectable
open var shadowColor: UIColor? {
get {
guard let v = layer.shadowColor else {
return nil
}
return UIColor(cgColor: v)
}
set(value) {
layer.shadowColor = value?.cgColor
}
}
/// A property that accesses the backing layer's shadowOffset.
@IBInspectable
open var shadowOffset: CGSize {
get {
return layer.shadowOffset
}
set(value) {
layer.shadowOffset = value
}
}
/// A property that accesses the backing layer's shadowOpacity.
@IBInspectable
open var shadowOpacity: Float {
get {
return layer.shadowOpacity
}
set(value) {
layer.shadowOpacity = value
}
}
/// A property that accesses the backing layer's shadowRadius.
@IBInspectable
open var shadowRadius: CGFloat {
get {
return layer.shadowRadius
}
set(value) {
layer.shadowRadius = value
}
}
/// A property that accesses the backing layer's shadowPath.
@IBInspectable
open var shadowPath: CGPath? {
get {
return layer.shadowPath
}
set(value) {
layer.shadowPath = value
}
}
/// A property that accesses the layer.cornerRadius.
@IBInspectable
open var cornerRadius: CGFloat {
get {
return layer.cornerRadius
}
set(value) {
layer.cornerRadius = value
}
}
/// A property that accesses the layer.borderWith.
@IBInspectable
open var borderWidth: CGFloat {
get {
return layer.borderWidth
}
set(value) {
layer.borderWidth = value
}
}
/// A property that accesses the layer.borderColor property.
@IBInspectable
open var borderColor: UIColor? {
get {
guard let bcolor = layer.borderColor else {
return nil
}
return UIColor(cgColor: bcolor)
}
set(value) {
layer.borderColor = value?.cgColor
}
}
}
試試這個它對我有用......
yourView.layer.shadowColor = UIColor.blackColor().CGColor
yourView.layer.shadowOpacity = 0.5
yourView.layer.shadowOffset = CGSize(width: 3, height: 3)
yourView.layer.shadowRadius = 05
yourView.layer.shadowPath = UIBezierPath(rect: yourView.bounds).CGPath
yourView.layer.shouldRasterize = true
在斯威夫特。 對我有用的是添加:
self.noteImage.layer.masksToBounds = false
所以,完整的代碼是:
self.noteImage.layer.masksToBounds = false
self.noteImage.layer.shadowColor = UIColor.redColor().CGColor
self.noteImage.layer.shadowOpacity = 0.5
self.noteImage.layer.shadowOffset = CGSize(width: 2, height: 2)
self.noteImage.layer.shadowRadius = 1
self.noteImage.layer.shadowPath = UIBezierPath(rect: noteImage.bounds).CGPath
self.noteImage.layer.shouldRasterize = true
除了 Frederic Adda 的解決方案之外,不要忘記將具有陰影的視圖定位到可以繪制陰影的超級視圖。 否則陰影將被剪掉。 我在自定義單元格中犯了這個錯誤,並認為解決方案是錯誤的,直到我在四周添加了 8px 的填充。
這是我的解決方案。 如果您有多種類型的視圖,例如 UIView、UIControl、UITableView 等,並且不想為它們中的每一個創建子類,或者您想以最小的代碼更改添加此效果,那么這可能是您的選擇正在找。
目標通道
#import <UIKit/UIKit.h>
@interface UIView (CornerAndShadow)
- (void)setCornerAndShadow;
@end
目標-厘米
#import "UIView+CornerAndShadow.h"
#import <Masonry.h>
@implementation UIView (CornerAndShadow)
- (void)setCornerAndShadow {
// constants
CGFloat fCornerRadius = 9.f;
// only work for views with superview
if (self.superview == nil) {
return;
}
// set corner
self.layer.cornerRadius = fCornerRadius;
self.layer.masksToBounds = YES;
// create and configure shadowView
UIView *shadowView = [UIView new];
shadowView.backgroundColor = self.backgroundColor; // just to make shadow visible
shadowView.layer.cornerRadius = fCornerRadius;
shadowView.layer.shadowColor = [UIColor redColor].CGColor;
shadowView.layer.shadowOffset = CGSizeMake(0, 3.f);
shadowView.layer.shadowOpacity = 0.5f;
shadowView.layer.shadowRadius = 5.f;
// put shadowView into superview right below self
[self.superview insertSubview:shadowView belowSubview:self];
// set shadowView's frame equal to self
[shadowView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self);
}];
// use this if you're not using autolayout, and can get real frame here
// shadowView.frame = self.frame;
}
@end
這是一個較舊的問題,但我會在您的自定義繪制方法中完成所有操作,如下所示。
如果我知道我想對圓形視圖應用陰影,我通常會這樣做(這當然意味着我不想使用masksToBounds
)
您也不必向層次結構添加額外的“陰影視圖”。
@IBDesignable
class RoundedView: UIView {
@IBInspectable
var cornerRadius: CGFloat = 0
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
// You could use custom IBInspectable attributes
// for the stroke and fill color.
context.setFillColor(UIColor.white.cgColor)
context.setStrokeColor(UIColor.orange.cgColor)
// Add a clipping path to get the rounded look
// you want.
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
// Fill and stroke your background.
let background = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
background.lineWidth = 2
background.fill()
background.stroke()
}
private func shadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowRadius = 5
layer.shadowOpacity = 0.5
layer.shadowOffset = CGSize.zero
}
override func awakeFromNib() {
super.awakeFromNib()
shadow()
}
}
您可以將此功能用於所有視圖。
extension UIView{
func radiusAndBorder(radius:CGFloat, color:UIColor = UIColor.clear) -> UIView{
var rounfView:UIView = self
rounfView.layer.cornerRadius = CGFloat(radius)
rounfView.layer.borderWidth = 1
rounfView.layer.borderColor = color.cgColor
rounfView.clipsToBounds = true
return rounfView
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.