วิธีใดเป็นวิธีที่ดีที่สุดในการเพิ่มเงาลงใน UIView ของฉัน


108

ฉันกำลังพยายามเพิ่มเงาตกในมุมมองที่ซ้อนทับกันมุมมองจะยุบลงทำให้สามารถมองเห็นเนื้อหาในมุมมองอื่นได้ในหลอดเลือดดำนี้ฉันต้องการview.clipsToBoundsเปิดต่อเพื่อที่เมื่อมุมมองยุบเนื้อหาของพวกเขาจะถูกตัดออก

ดูเหมือนว่าจะทำให้ฉันเพิ่มเงาหล่นลงในเลเยอร์ได้ยากเพราะเมื่อฉันเปิดclipsToBoundsเงาก็จะถูกตัดออกไปด้วย

ฉันพยายามปรับแต่งview.frameและview.boundsเพื่อเพิ่มเงาหล่นลงในเฟรม แต่ปล่อยให้ขอบเขตมีขนาดใหญ่พอที่จะล้อมรอบได้ แต่ฉันไม่มีโชคกับเรื่องนี้

นี่คือรหัสที่ฉันใช้เพื่อเพิ่ม Shadow (ใช้ได้กับclipsToBoundsOFF เท่านั้นตามที่แสดง)

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ใช้เป็น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;เปิดไว้- ทั้งคู่ไม่สามารถตัดเนื้อหาได้ นี่คือภาพหน้าจอของสิ่งที่เกิดขึ้นกับตัวอย่างของคุณ> 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

คำตอบของ Wasabii ใน 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
หรือดีกว่าในมุมมองDidLayoutSubviews ()
Max MacLeod

1
ชอบตัวเริ่มต้นโครงสร้างที่รวดเร็วมากกว่าสร้างตัวแปรเช่นCGSize(width: 0, height: 0.5)
Oliver Atkinson

ฉันเพิ่มรหัสนี้ใน layoutSubviews () แต่ก็ยังไม่แสดงผลใน IB มีการตั้งค่าอื่น ๆ ที่ฉันต้องเลือกใช้หรือไม่?
ลา

ขอบคุณ. ฉันใช้การจัดวางอัตโนมัติและฉันคิดกับตัวเอง - อืม .. view.boundsยังไม่ได้สร้างขึ้นอย่างชัดเจนจนshadowPathไม่สามารถอ้างอิง rect ของมุมมองได้ แต่มีบางส่วนCGRectZeroแทน อย่างไรก็ตามการวางสิ่งนี้ไว้layoutSubviewsช่วยแก้ปัญหาของฉันได้ในที่สุด!
devdc


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 ทำให้พร้อมใช้งานในตัวสร้างอินเทอร์เฟซ
Nitesh

ฉันสามารถบรรลุเป้าหมายนี้ใน Objective C ได้หรือไม่?
Harish Pathak

2
หากคุณต้องการดูตัวอย่างแบบเรียลไทม์ (ในตัวสร้างอินเทอร์เฟซ) คุณจะพบบทความดีๆเกี่ยวกับมันได้ที่นี่ spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill


2

ในมุมมอง WillLayoutSubviews:

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)

1
เพียงแค่บังคับtargetView: UIViewและเอาผมหน้าม้าออก
bluebamboo

6
ทำไมไม่ใช้self? ดูเหมือนสะดวกกว่ามากสำหรับฉัน
LinusGeffarth

3
วิธีที่ดีกว่าในการทำเช่นนี้คือการลบ targetView เป็นพารามิเตอร์และใช้ self แทน targetView ซึ่งจะช่วยลดความซ้ำซ้อนและช่วยให้สามารถเรียกมันได้เช่นนั้น: sampleView.addDropShadowToView ()
maxcodes

ความคิดเห็นของ Max Nelson นั้นยอดเยี่ยมมาก คำแนะนำของฉันคือใช้if letไวยากรณ์แทน!
จอห์นนี่

0

ใช่คุณควรชอบคุณสมบัติ shadowPath เพื่อประสิทธิภาพ แต่ยัง: จากไฟล์ส่วนหัวของ CALayer.shadowPath

การระบุเส้นทางโดยชัดแจ้งโดยใช้คุณสมบัตินี้โดยปกติจะ * ปรับปรุงประสิทธิภาพการแสดงผลเนื่องจากจะแชร์การอ้างอิงเส้นทางเดียวกันในหลายเลเยอร์

เคล็ดลับที่รู้จักกันน้อยคือการแชร์ข้อมูลอ้างอิงเดียวกันในหลายเลเยอร์ แน่นอนว่าต้องใช้รูปร่างเดียวกัน แต่เป็นเรื่องปกติสำหรับเซลล์มุมมองตาราง / คอลเลกชัน

ฉันไม่รู้ว่าทำไมมันถึงเร็วขึ้นถ้าคุณแชร์อินสแตนซ์ฉันเดาว่ามันแคชการเรนเดอร์ของเงาและสามารถใช้ซ้ำกับอินสแตนซ์อื่น ๆ ในมุมมองได้ ฉันสงสัยว่านี่จะเร็วกว่าด้วยหรือไม่

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