วิธีควบคุมเงากระจายและเบลอ


113

ฉันได้ออกแบบองค์ประกอบ UI ในแบบร่างและหนึ่งในนั้นมีเงาที่มีความเบลอ 1 และการแพร่กระจาย 0 ฉันดูที่เอกสารสำหรับคุณสมบัติเลเยอร์มุมมองและเลเยอร์ไม่มีชื่ออะไรแพร่กระจายหรือเบลอหรืออะไรที่เทียบเท่า (ตัวควบคุมเดียวคือ only shadowOpacity) จะควบคุมสิ่งต่างๆเช่นเบลอและกระจายได้อย่างไร?

แก้ไข:

นี่คือการตั้งค่าของฉันใน Sketch: และนี่คือสิ่งที่ฉันต้องการให้เงาของฉันดูเหมือน:การตั้งค่าเงาร่าง


ต้องการเงา




และนี่คือสิ่งที่ดูเหมือนในขณะนี้: หมายเหตุคุณต้องคลิกที่ภาพเพื่อดูเงาจริงๆ

เงาปัจจุบัน

รหัสของฉันมีดังนี้:

func setupLayer(){
    view.layer.cornerRadius = 2
    view.layer.shadowColor = Colors.Shadow.CGColor
    view.layer.shadowOffset = CGSize(width: 0, height: 1)
    view.layer.shadowOpacity = 0.9
    view.layer.shadowRadius = 5
}

แท็ก iOS (แพลตฟอร์ม) การออกแบบ (การใช้ซอฟต์แวร์ Sketch) และ Core-Graphics (เป็นไปได้ที่จะใช้ UIBezierPath เพื่อวาดเงาซึ่งอาจเกี่ยวข้อง) มีความเกี่ยวข้องทั้งหมดฉันไม่เห็นว่าทำไมจึงควร ถูกลบออก
Quantaliinuxite

คุณต้องการเงาสำหรับมุมมองสีขาวนั้นใช่ไหม
O-mkar

5
ดูเหมือนว่า Sketch และ CoreAnimation framework จะมีเมตริกที่แตกต่างกันเนื่องจากมักจะดูแตกต่างกันใน iOS และใน Sketch ที่มีพารามิเตอร์เดียวกัน
Timur Bernikovich

อา. ความสุขในการทำงานร่วมกับนักออกแบบที่ใช้เครื่องมือที่มีความคล้ายคลึงกับการทำงานของ iOS เพียงเล็กน้อยหรือไม่มีเลย หากคุณย้ายไปที่บางอย่างเช่น PaintCode แทนที่จะเป็น Sketch มันจะไม่เพียง แต่ทำงานเหมือนกับ iOS เท่านั้น แต่ยังให้รหัสที่คุณต้องการด้วย :-)
Fogmeister

จะเกิดอะไรขึ้นถ้าคุณตั้งค่าทั้งรัศมีและความเบลอในภาพร่าง?
Vladimir

คำตอบ:


261

ต่อไปนี้คือวิธีใช้คุณสมบัติเงาร่างทั้ง 6 กับเลเยอร์ของ UIView ที่มีความแม่นยำใกล้เคียงกัน:

extension CALayer {
  func applySketchShadow(
    color: UIColor = .black,
    alpha: Float = 0.5,
    x: CGFloat = 0,
    y: CGFloat = 2,
    blur: CGFloat = 4,
    spread: CGFloat = 0)
  {
    shadowColor = color.cgColor
    shadowOpacity = alpha
    shadowOffset = CGSize(width: x, height: y)
    shadowRadius = blur / 2.0
    if spread == 0 {
      shadowPath = nil
    } else {
      let dx = -spread
      let rect = bounds.insetBy(dx: dx, dy: dx)
      shadowPath = UIBezierPath(rect: rect).cgPath
    }
  }
}

สมมติว่าเราต้องการแสดงสิ่งต่อไปนี้:

ใส่คำอธิบายภาพที่นี่

คุณสามารถทำได้ง่ายๆผ่าน:

myView.layer.applySketchShadow(
  color: .black, 
  alpha: 0.5, 
  x: 0, 
  y: 0, 
  blur: 4, 
  spread: 0)

หรือสั้นกว่านั้น:

myView.layer.applySketchShadow(y: 0)

ตัวอย่าง:

ใส่คำอธิบายภาพที่นี่

ซ้าย: ภาพหน้าจอ iPhone 8 UIView; ขวา: ร่างสี่เหลี่ยมผืนผ้า

บันทึก:

  • เมื่อใช้ค่าที่ไม่ใช่ศูนย์spreadจะฮาร์ดโค้ดพา ธ ตามboundsCALayer หากขอบเขตของเลเยอร์เปลี่ยนไปคุณจะต้องเรียกใช้applySketchShadow()เมธอดนั้นอีกครั้ง

หืม ใน Sketch ถ้าคุณใช้การแพร่กระจายมันจะ 'ย้ายออก' ซึ่งการเบลอ / ไล่ระดับของเงาจะเริ่ม ซึ่งหมายความว่า - มันจะอยู่ที่สีเงาคงที่จนกว่าความเบลอจะเริ่มขึ้น แต่ถ้าเราเลื่อน shadowLayer ไปรอบ ๆ เงาก็จะเริ่มจากเลเยอร์นั้นเท่านั้นใช่ไหม? (พูดว่าสเปรดคือ 10 เงาจะเริ่มหลังจากออฟเซ็ต 10 เท่านั้นโดยที่ใน Sketch การเบลอ / การไล่ระดับสีจะเริ่มหลังจากออฟเซ็ต 10) ดังนั้นพวกเขาจึงไม่ตรงกันจริงไหม?
Stephan

1
@Senseful ฉันสนใจที่จะรู้ว่าคุณบรรลุสูตรนี้ได้อย่างไร!
Hashem Aboonajmi

2
maskToBounds = falseคุณลืมชุด
ScottyBlades

1
ดังที่ได้กล่าวข้างต้นโดย ScottyBlades maskToBounds = falseนี้จะไม่ทำงานโดยไม่ต้อง ตัวอย่างที่จะล้มเหลวคือเมื่อใช้เงานี้กับUIButtonไฟล์.
José

5
สิ่งนี้ควรshadowRadius = blur / UIScreen.main.scaleตรงข้ามกับการเข้ารหัสblur / 2.0หรือไม่ หรือมีเหตุผลอื่นที่จะหารด้วย 2? @matchifang คุณคิดออกหรือยัง?
JWK

58

ลองดู .... เล่นได้เลยค่า 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

ตัวอย่าง Basic Shadow ใน Swift 2.0

เอาท์พุท


2
ข้อมูลไม่ถูกต้องออฟเซ็ตไม่ใช่ตัวควบคุมการแพร่กระจาย แต่เป็นการควบคุม x และ y
Quantaliinuxite

@Quantaliinuxite ฉันบังเอิญเปลี่ยนความคิดเห็นฉันได้อัปเดตคำตอบ
O-mkar

ตอนนี้ประเด็นหลักของคำถามคือฉันจะควบคุมการแพร่กระจายได้อย่างไร
Quantaliinuxite

@Quantaliinuxite อัปเดตโค้ดแล้วตอนนี้เปลี่ยน 2.1 เป็นจำนวนส
เปรดที่

ขอบคุณสำหรับคำตอบ. ที่นี่ฉันไม่สามารถเข้าใจ shadowOpacity (ทำไมถึงใช้)
Sunita

13

Sketch Shadow โดยใช้ IBDesignable และ IBInspectable ใน Swift 4

สเก็ตช์และ 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
            }
        }
    }
}

เอาท์พุท

เอาท์พุทสาธิต

วิธีใช้งาน

การสาธิต


คุณไม่ควรกลับไปlayer.shadowRadius * 2หา shadowBlur getter
berkuqo

7

รหัสนี้ทำงานได้ดีสำหรับฉัน:

yourView.layer.shadowOpacity = 0.2 // opacity, 20%
yourView.layer.shadowColor = UIColor.black.cgColor
yourView.layer.shadowRadius = 2 // HALF of blur
yourView.layer.shadowOffset = CGSize(width: 0, height: 2) // Spread x, y
yourView.layer.masksToBounds = false

ค่าความเบลอใน Sketch ไม่ตรงกับรัศมีเงาของ Core Animation
CIFilter

สิ่งที่ทำงานได้ใกล้พอสำหรับเราคือการลดค่าเบลอครึ่งหนึ่งในรัศมีเงาของ Sketch for Core Animation
CIFilter

2

สำหรับผู้ที่พยายามใช้เงากับเส้นทางที่กำหนดไว้ล่วงหน้า (เช่นสำหรับมุมมองวงกลมเป็นต้น) นี่คือสิ่งที่ฉันลงเอยด้วย:

extension CALayer {
    func applyShadow(color: UIColor = .black,
                     alpha: Float = 0.5,
                     x: CGFloat = 0,
                     y: CGFloat = 2,
                     blur: CGFloat = 4,
                     spread: CGFloat = 0,
                     path: UIBezierPath? = nil) {
        shadowColor = color.cgColor
        shadowOpacity = alpha
        shadowRadius = blur / 2
        if let path = path {
            if spread == 0 {
                shadowOffset = CGSize(width: x, height: y)
            } else {
                let scaleX = (path.bounds.width + (spread * 2)) / path.bounds.width
                let scaleY = (path.bounds.height + (spread * 2)) / path.bounds.height

                path.apply(CGAffineTransform(translationX: x + -spread, y: y + -spread).scaledBy(x: scaleX, y: scaleY))
                shadowPath = path.cgPath
            }
        } else {
            shadowOffset = CGSize(width: x, height: y)
            if spread == 0 {
                shadowPath = nil
            } else {
                let dx = -spread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
        shouldRasterize = true
        rasterizationScale = UIScreen.main.scale
    }
}

ฉันจะโพสต์ตัวอย่างบางส่วนในภายหลัง แต่สิ่งนี้ได้ผลสำหรับมุมมองแบบวงกลมสำหรับฉัน


นี้ "เกือบ" ผลงาน แต่การแพร่กระจายที่ไม่ถูกต้องเมื่อคุณใช้การแพร่กระจายของ1ด้วยและx y 0 shapeLayer.applyShadow(color: .black, alpha: 1, x: 0, y: 0, blur: 0, spread: 1, path: path)ในกรณีนี้เงาจะมีการชดเชยในขณะที่ไม่ควร สามเหลี่ยมควรมีเงากระจาย 1px ในทุกทิศทางibb.co/YjwRb9B
ม.ค.

1

วิธีแก้ปัญหาของฉันตามการตอบกลับโพสต์นี้: (Swift 3)

let shadowPath = UIBezierPath(rect: CGRect(x: -1,
                                           y: -2,
                                           width: target.frame.width + 2,
                                           height: target.frame.height + 2))

target.layer.shadowColor = UIColor(hexString: shadowColor).cgColor
target.layer.shadowOffset = CGSize(width: CGFloat(shadowOffsetX), height: CGFloat(shadowOffsetY))
target.layer.masksToBounds = false
target.layer.shadowOpacity = Float(shadowOpacity)
target.layer.shadowPath = shadowPath.cgPath

0

ฉันชอบคำตอบที่โพสต์ไว้ที่นี่และข้อเสนอแนะในความคิดเห็น นี่คือวิธีที่ฉันแก้ไขโซลูชันนั้น:

extension UIView {
  func applyShadow(color: UIColor, alpha: Float, x: CGFloat, y: CGFloat, blur: CGFloat, spread: CGFloat) {
    layer.masksToBounds = false
    layer.shadowColor = color.cgColor
    layer.shadowOpacity = alpha
    layer.shadowOffset = CGSize(width: x, height: y)
    layer.shadowRadius = blur / UIScreen.main.scale
    if spread == 0 {
      layer.shadowPath = nil
    } else {
      let dx = -spread
      let rect = bounds.insetBy(dx: dx, dy: dx)
      layer.shadowPath = UIBezierPath(rect: rect).cgPath
    }
  }
}

การใช้งาน:

myButton.applyShadow(color: .black, alpha: 0.2, x: 0, y: 1, blur: 2, spread: 0)

-1

อาจเป็นเรื่องเล็กน้อยในประวัติศาสตร์ แต่บางคนอาจมีปัญหาเดียวกัน ฉันใช้ตัวอย่างโค้ดจากคำตอบที่ยอมรับ อย่างไรก็ตามเอฟเฟกต์แตกต่างกันมาก: - ค่า y จะต้องอยู่ที่ประมาณครึ่งหนึ่งเมื่อเทียบกับค่าเดียวกันในร่าง - ฉันพยายามใช้เงาบนแถบนำทางและเอฟเฟกต์แตกต่างกันมากแทบจะมองไม่เห็นในขณะที่ใช้ค่าเดียวกันกับที่ร่างมี

ดูเหมือนว่าวิธีนี้จะไม่สะท้อนพารามิเตอร์ร่างโดยสิ้นเชิง คำแนะนำใด ๆ ?


นี่ไม่ใช่คำตอบ - คอมเมนต์เข้าไป!
Dave Y
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.