เอฟเฟกต์เงาด้านในบนเลเยอร์ UIView?


92

ฉันมี CALayer ดังต่อไปนี้:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithObjects:(id)[RGB(130, 0, 140) CGColor], (id)[RGB(108, 0, 120) CGColor], nil];
[self.layer insertSublayer:gradient atIndex:0];

ฉันต้องการเพิ่มเอฟเฟกต์เงาภายในแต่ฉันไม่ค่อยแน่ใจว่าจะทำอย่างไร ฉันคิดว่าฉันจะต้องวาดใน drawRect อย่างไรก็ตามสิ่งนี้จะเพิ่มเลเยอร์ที่ด้านบนของวัตถุ UIView อื่น ๆ เนื่องจากควรจะเป็นแถบที่อยู่ด้านหลังปุ่มบางปุ่มดังนั้นฉันจึงสูญเสียสิ่งที่ต้องทำ?

ฉันสามารถเพิ่มเลเยอร์อื่นได้ แต่ไม่แน่ใจว่าจะบรรลุเอฟเฟกต์เงาภายในได้อย่างไร (เช่นนี้:

ป้อนคำอธิบายภาพที่นี่

ช่วยชื่นชม ...

คำตอบ:


108

สำหรับใครก็ตามที่สงสัยว่าจะวาดเงาภายในโดยใช้ Core Graphics ได้อย่างไรตามคำแนะนำของ Costique นี่คือวิธี: (บน iOS ปรับได้ตามต้องการ)

ใน drawRect ของคุณ: วิธีการ ...

CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 0.5f * CGRectGetHeight(bounds);


// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);

// Fill this path
UIColor *aColor = [UIColor redColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextFillPath(context);


// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);

// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath); 
CGContextClip(context);         


// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 3.0f, [aColor CGColor]);   

// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];   
CGContextSaveGState(context);   
CGContextAddPath(context, path);
CGContextEOFillPath(context);

// Release the paths
CGPathRelease(path);    
CGPathRelease(visiblePath);

ดังนั้นโดยพื้นฐานแล้วมีขั้นตอนต่อไปนี้:

  1. สร้างเส้นทางของคุณ
  2. กำหนดสีเติมที่คุณต้องการเพิ่มเส้นทางนี้ไปยังบริบทและเติมบริบท
  3. ตอนนี้สร้างสี่เหลี่ยมผืนผ้าที่ใหญ่ขึ้นซึ่งสามารถผูกเส้นทางที่มองเห็นได้ ก่อนปิดเส้นทางนี้ให้เพิ่มเส้นทางที่มองเห็นได้ จากนั้นปิดเส้นทางเพื่อให้คุณสร้างรูปร่างโดยลบเส้นทางที่มองเห็นออกจากมัน คุณอาจต้องการตรวจสอบวิธีการเติม (การคดเคี้ยวที่ไม่ใช่ศูนย์ของคู่ / คี่) ขึ้นอยู่กับว่าคุณสร้างเส้นทางเหล่านี้อย่างไร โดยพื้นฐานแล้วในการทำให้ subpaths เป็น "ลบ" เมื่อคุณรวมเข้าด้วยกันคุณจะต้องวาด (หรือสร้างมากกว่านั้น) ในทิศทางตรงกันข้ามตามเข็มนาฬิกาและอีกทางหนึ่งทวนเข็มนาฬิกา
  4. จากนั้นคุณต้องตั้งค่าเส้นทางที่มองเห็นของคุณเป็นเส้นทางการตัดบนบริบทเพื่อที่คุณจะได้ไม่วาดอะไรออกไปนอกหน้าจอ
  5. จากนั้นตั้งค่าเงาบนบริบทซึ่งรวมถึงออฟเซ็ตเบลอและสี
  6. จากนั้นเติมรูปทรงที่ใหญ่ด้วยรูเข้าไป สีไม่สำคัญเพราะถ้าคุณทำทุกอย่างถูกต้องคุณจะไม่เห็นสีนี้เป็นเพียงเงา

ขอบคุณ แต่เป็นไปได้ไหมที่จะปรับรัศมี ปัจจุบันขึ้นอยู่กับขอบเขต แต่ฉันต้องการอิงตามรัศมีที่ตั้งไว้แทน (เช่น 5.0f) ด้วยรหัสข้างต้นมันจะกลมมากเกินไป
runmad

2
@runmad คุณสามารถสร้าง CGPath ที่มองเห็นได้ทุกประเภทที่คุณต้องการตัวอย่างที่ใช้ที่นี่เป็นเพียงตัวอย่างที่เลือกเพื่อความกะทัดรัด หากคุณต้องการสร้างสี่เหลี่ยมผืนผ้าโค้งมนคุณสามารถทำบางสิ่งเช่น: CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect: rect cornerRadius: radius] .CGPath หวังว่าจะช่วยได้
Daniel Thorpe

4
@DanielThorpe: +1 สำหรับคำตอบที่ดี ฉันแก้ไขรหัสพา ธ สี่เหลี่ยมผืนผ้าโค้งมน (ของคุณพังเมื่อเปลี่ยนรัศมี) และทำให้โค้ดพา ธ ด้านนอกง่ายขึ้น หวังว่าคุณจะไม่รังเกียจ
Regexident

ฉันจะตั้งค่าเงาภายในอย่างถูกต้องจาก 4 ทิศทางไม่ใช่แค่ 2 ทิศทางได้อย่างไร
Protocole

@ Protocole คุณสามารถตั้งค่าออฟเซ็ตเป็น {0,0} แต่ใช้รัศมีเงาของการพูด 4.f.
Daniel Thorpe

47

ฉันรู้ว่าฉันมางานปาร์ตี้นี้สาย แต่นี่จะช่วยให้ฉันได้พบกับการเดินทางในช่วงต้น ...

ในการให้เครดิตในกรณีที่เครดิตครบกำหนดนี่คือการปรับเปลี่ยนรายละเอียดของ Daniel Thorpe เกี่ยวกับโซลูชันของ Costique ในการลบพื้นที่ที่เล็กกว่าออกจากภูมิภาคที่ใหญ่ เวอร์ชันนี้มีไว้สำหรับผู้ที่ใช้การจัดองค์ประกอบเลเยอร์แทนการลบล้าง-drawRect:

CAShapeLayerระดับสามารถนำมาใช้เพื่อให้บรรลุผลเช่นเดียวกัน:

CAShapeLayer* shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:[self bounds]];

// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:5];

// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];

// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);

[shadowLayer setPath:path];
CGPathRelease(path);

[[self layer] addSublayer:shadowLayer];

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

CAShapeLayer* maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];

ฉันไม่ได้ benchmarked นี้เอง แต่ผมสงสัยว่าใช้วิธีนี้ร่วมกับแรสเตอร์เป็น performant -drawRect:กว่าเอาชนะ


3
someInnerPath? คุณช่วยอธิบายเพิ่มเติมอีกนิดได้ไหม
Moe

4
@ Mo สามารถเป็น CGPath ใดก็ได้ที่คุณต้องการ [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]เป็นตัวเลือกที่ง่ายที่สุด
Matt Wilding

Cheers for that Matt :-)
Moe

ฉันได้รับสี่เหลี่ยมผืนผ้าสีดำ (ด้านนอก) สำหรับ shadowLayer.path ที่วาดเงาด้านในอย่างถูกต้อง ฉันจะกำจัดมันได้อย่างไร (สี่เหลี่ยมผืนผ้าด้านนอกสีดำ)? ดูเหมือนว่าคุณสามารถตั้งค่าสีเติมภายในบริบทได้เท่านั้นและคุณไม่ได้ใช้สีเดียว
Olivier

11
ใช้งานได้ดีมาก! ฉันอัปโหลดไปยัง github พร้อมส่วนเสริมบางอย่าง ลองดู :) github.com/inamiy/YIInnerShadowView
inamiy

35

เป็นไปได้ที่จะวาดเงาภายในด้วย Core Graphics โดยสร้างเส้นทางสี่เหลี่ยมผืนผ้าขนาดใหญ่นอกขอบเขตลบเส้นทางสี่เหลี่ยมผืนผ้าขนาดขอบเขตและเติมเส้นทางผลลัพธ์โดยมีเงา "ปกติ" อยู่

อย่างไรก็ตามเนื่องจากคุณต้องรวมกับเลเยอร์ไล่ระดับสีฉันคิดว่าวิธีแก้ปัญหาที่ง่ายกว่าคือการสร้างภาพเงาด้านใน PNG โปร่งใส 9 ส่วนและยืดให้มีขนาดที่เหมาะสม ภาพเงา 9 ส่วนจะมีลักษณะดังนี้ (ขนาด 21x21 พิกเซล):

ข้อความแสดงแทน

CALayer *innerShadowLayer = [CALayer layer];
innerShadowLayer.contents = (id)[UIImage imageNamed: @"innershadow.png"].CGImage;
innerShadowLayer.contentsCenter = CGRectMake(10.0f/21.0f, 10.0f/21.0f, 1.0f/21.0f, 1.0f/21.0f);

จากนั้นตั้งกรอบ innerShadowLayer และควรยืดเงาให้เหมาะสม


ใช่ฉันคิดว่าคุณพูดถูก แค่อยากให้เลเยอร์แบนที่สุด ฉันสามารถสร้างภาพใน Photoshop ด้วยเงาด้านในและการไล่ระดับสีได้ฉันเพิ่งมีปัญหาเกี่ยวกับสีที่ตรงกัน 100% บนอุปกรณ์เมื่อใช้รูปภาพ
runmad

ใช่นั่นเป็นปัญหากับการไล่ระดับสีและเงาทั้งหมดฉันไม่สามารถสร้างเอฟเฟกต์ Photoshop เหล่านี้แบบ 1: 1 บน iOS ได้ยากเท่าที่ฉันลอง
Costique

31

เวอร์ชันที่เรียบง่ายโดยใช้เพียง CALayer ใน Swift:

import UIKit

final class FrameView : UIView {
    init() {
        super.init(frame: CGRect.zero)
        backgroundColor = UIColor.white
    }

    @available(*, unavailable)
    required init?(coder decoder: NSCoder) { fatalError("unavailable") }

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

    private func addInnerShadow() {
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        // Shadow path (1pt ring around bounds)
        let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
        let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
        path.append(cutout)
        innerShadow.shadowPath = path.cgPath
        innerShadow.masksToBounds = true
        // Shadow properties
        innerShadow.shadowColor = UIColor(white: 0, alpha: 1).cgColor // UIColor(red: 0.71, green: 0.77, blue: 0.81, alpha: 1.0).cgColor
        innerShadow.shadowOffset = CGSize.zero
        innerShadow.shadowOpacity = 1
        innerShadow.shadowRadius = 3
        // Add
        layer.addSublayer(innerShadow)
    }
}

โปรดทราบว่าเลเยอร์ innerShadow ไม่ควรมีสีพื้นหลังทึบแสงเนื่องจากจะแสดงผลที่ด้านหน้าของเงา


บรรทัดสุดท้ายมี "เลเยอร์" สิ่งนี้มาจากไหน?
Charlie Seligman

@CharlieSeligman เป็นเลเยอร์หลักซึ่งอาจเป็นเลเยอร์ใดก็ได้ คุณสามารถใช้เลเยอร์ที่กำหนดเองหรือเลเยอร์ของมุมมอง (UIView มีคุณสมบัติของเลเยอร์)
Patrick Pijnappel

let innerShadow = CALayer(); innerShadow.frame = boundsควรจะเป็น หากไม่มีขอบเขตที่เหมาะสมมันจะไม่วาดเงาที่เหมาะสม ขอบคุณมาก
haik.ampardjian

@noir_eagle จริงอยู่แม้ว่าคุณอาจต้องการตั้งค่านั้นlayoutSubviews()เพื่อให้มันซิงค์
Patrick Pijnappel

ขวา! ไม่ว่าจะในlayoutSubviews()หรือdraw(_ rect)
haik.ampardjian

24

ค่อนข้างกลม แต่หลีกเลี่ยงการใช้ภาพ (อ่าน: เปลี่ยนสีง่ายรัศมีเงา ฯลฯ ) และรหัสเพียงไม่กี่บรรทัด

  1. เพิ่ม UIImageView เป็นมุมมองย่อยแรกของ UIView ที่คุณต้องการให้ใช้งาน dropshadow ฉันใช้ IB แต่คุณสามารถทำแบบเดียวกันนี้ได้โดยใช้โปรแกรม

  2. สมมติว่าการอ้างอิงถึง UIImageView คือ 'innerShadow'

`

[[innerShadow layer] setMasksToBounds:YES];
[[innerShadow layer] setCornerRadius:12.0f];        
[[innerShadow layer] setBorderColor:[UIColorFromRGB(180, 180, 180) CGColor]];
[[innerShadow layer] setBorderWidth:1.0f];
[[innerShadow layer] setShadowColor:[UIColorFromRGB(0, 0, 0) CGColor]];
[[innerShadow layer] setShadowOffset:CGSizeMake(0, 0)];
[[innerShadow layer] setShadowOpacity:1];
[[innerShadow layer] setShadowRadius:2.0];

ข้อแม้: คุณต้องมีเส้นขอบมิฉะนั้นเงาจะไม่ปรากฏขึ้น [UIColor clearColor] ไม่ทำงาน ในตัวอย่างฉันใช้สีอื่น แต่คุณสามารถยุ่งกับมันเพื่อให้มันมีสีเดียวกับจุดเริ่มต้นของเงา :)

ดูความคิดเห็นของ bbrame ด้านล่างเกี่ยวกับUIColorFromRGBมาโคร


ฉันทิ้งมันไว้ แต่สมมติว่าคุณทำสิ่งนี้เป็นส่วนหนึ่งของการเพิ่มมุมมองภาพ - ตรวจสอบให้แน่ใจว่าได้ตั้งค่าเฟรมเป็นสี่เหลี่ยมผืนผ้าเดียวกับ UIView หลัก หากคุณใช้ IB ให้ตั้งค่าเสาและสปริงให้เหมาะสมเพื่อให้มีขนาดเงาพร้อมกับมุมมองหากคุณจะเปลี่ยนเฟรมของมุมมองหลัก ในโค้ดควรมีมาสก์ปรับขนาดที่คุณสามารถทำได้หรือทำเช่นเดียวกัน AFAIK
jinglesthula

นี่เป็นวิธีที่ง่ายที่สุดในตอนนี้ แต่โปรดทราบว่าวิธีการเงาของ CALayer มีให้ใช้งานใน iOS 3.2 และใหม่กว่าเท่านั้น ฉันสนับสนุน 3.1 ดังนั้นฉันจึงตั้งค่าแอตทริบิวต์เหล่านี้ใน if ([layer responseToSelector: @selector (setShadowColor :)]) {
DougW

ดูเหมือนจะไม่ได้ผลสำหรับฉัน อย่างน้อยใน xcode 4.2 และ ios simulator 4.3 ในการทำให้เงาปรากฏขึ้นฉันต้องเพิ่มสีพื้นหลัง ... ณ จุดที่ดรอปแชโดว์ปรากฏเฉพาะภายนอก
Andrea

@Andrea - จำข้อแม้ที่ฉันได้กล่าวไว้ข้างต้น ฉันคิดว่าสีพื้นหลังหรือเส้นขอบอาจมีผลเหมือนกับการ "ให้บางสิ่งบางอย่างเพื่อเพิ่มเงาให้กับ" สำหรับสิ่งที่ปรากฏภายนอกหาก UIImageView ไม่ใช่มุมมองย่อยของมุมมองที่คุณต้องการเงาภายในนั่นอาจเป็นได้ - ฉันต้องดูรหัสของคุณเพื่อดู
jinglesthula

เพียงเพื่อแก้ไขคำสั่งก่อนหน้าของฉัน ... รหัสใช้งานได้จริง ... ฉันพลาดบางอย่างไป แต่น่าเสียดายที่ฉันจำไม่ได้ในตอนนี้ :) ดังนั้น ... ขอบคุณสำหรับการแบ่งปันข้อมูลโค้ดนี้
Andrea

17

มาสายดีกว่าไม่มาเลย...

นี่เป็นอีกแนวทางหนึ่งซึ่งอาจจะไม่ดีไปกว่าที่โพสต์ไว้แล้ว แต่ก็ดีและเรียบง่าย -

-(void)drawInnerShadowOnView:(UIView *)view
{
    UIImageView *innerShadowView = [[UIImageView alloc] initWithFrame:view.bounds];

    innerShadowView.contentMode = UIViewContentModeScaleToFill;
    innerShadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [view addSubview:innerShadowView];

    [innerShadowView.layer setMasksToBounds:YES];

    [innerShadowView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
    [innerShadowView.layer setShadowColor:[UIColor blackColor].CGColor];
    [innerShadowView.layer setBorderWidth:1.0f];

    [innerShadowView.layer setShadowOffset:CGSizeMake(0, 0)];
    [innerShadowView.layer setShadowOpacity:1.0];

    // this is the inner shadow thickness
    [innerShadowView.layer setShadowRadius:1.5];
}

@SomaMan เป็นไปได้ไหมที่จะตั้งเงาเฉพาะด้านเท่านั้น? ชอบเฉพาะบนหรือบน / ล่างหรือบน / ขวาเป็นต้น ..
Mitesh Dobareeya

8

แทนที่จะวาดเงาภายในโดย drawRect หรือเพิ่ม UIView ไปที่ View คุณสามารถเพิ่ม CALayer ลงในเส้นขอบได้โดยตรงตัวอย่างเช่นถ้าฉันต้องการเอฟเฟกต์เงาภายในที่ด้านล่างของ UIView V

innerShadowOwnerLayer = [[CALayer alloc]init];
innerShadowOwnerLayer.frame = CGRectMake(0, V.frame.size.height+2, V.frame.size.width, 2);
innerShadowOwnerLayer.backgroundColor = [UIColor whiteColor].CGColor;

innerShadowOwnerLayer.shadowColor = [UIColor blackColor].CGColor;
innerShadowOwnerLayer.shadowOffset = CGSizeMake(0, 0);
innerShadowOwnerLayer.shadowRadius = 10.0;
innerShadowOwnerLayer.shadowOpacity = 0.7;

[V.layer addSubLayer:innerShadowOwnerLayer];

เพิ่มเงาด้านในด้านล่างสำหรับ UIView เป้าหมาย


6

นี่คือเวอร์ชันของการเปลี่ยนแปลงstartPointและการเปลี่ยนแปลงที่รวดเร็วendPointในแต่ละด้าน

        let layer = CAGradientLayer()
        layer.startPoint    = CGPointMake(0.5, 0.0);
        layer.endPoint      = CGPointMake(0.5, 1.0);
        layer.colors        = [UIColor(white: 0.1, alpha: 1.0).CGColor, UIColor(white: 0.1, alpha: 0.5).CGColor, UIColor.clearColor().CGColor]
        layer.locations     = [0.05, 0.2, 1.0 ]
        layer.frame         = CGRectMake(0, 0, self.view.frame.width, 60)
        self.view.layer.insertSublayer(layer, atIndex: 0)

ทำงานให้ฉัน !! ขอขอบคุณ.
iUser

5

นี่คือโซลูชันของคุณซึ่งฉันได้ส่งออกจากPaintCode :

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Shadow Declarations
    UIColor* shadow = UIColor.whiteColor;
    CGSize shadowOffset = CGSizeMake(0, 0);
    CGFloat shadowBlurRadius = 10;

    //// Rectangle Drawing
    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.bounds];
    [[UIColor blackColor] setFill];
    [rectanglePath fill];

    ////// Rectangle Inner Shadow
    CGContextSaveGState(context);
    UIRectClip(rectanglePath.bounds);
    CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);

    CGContextSetAlpha(context, CGColorGetAlpha([shadow CGColor]));
    CGContextBeginTransparencyLayer(context, NULL);
    {
        UIColor* opaqueShadow = [shadow colorWithAlphaComponent: 1];
        CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [opaqueShadow CGColor]);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextBeginTransparencyLayer(context, NULL);

        [opaqueShadow setFill];
        [rectanglePath fill];

        CGContextEndTransparencyLayer(context);
    }
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
}

3

ฉันไปงานปาร์ตี้ช้ามาก แต่ฉันต้องการตอบแทนชุมชน .. นี่เป็นวิธีที่ฉันเขียนเพื่อลบภาพพื้นหลัง UITextField เนื่องจากฉันกำลังจัดหา Static Library และ NO Resources ... ฉันใช้สำหรับ หน้าจอการป้อน PIN ของอินสแตนซ์ UITextField สี่อินสแตนซ์ที่สามารถแสดงอักขระหนึ่งตัวแบบ raw หรือ (BOOL) [self isUsingBullets] หรือ (BOOL) [self usingAsterisks] ใน ViewController แอพนี้มีไว้สำหรับ iPhone / iPhone retina / iPad / iPad Retina ดังนั้นฉันไม่ต้องใส่สี่ภาพ ...

#import <QuartzCore/QuartzCore.h>

- (void)setTextFieldInnerGradient:(UITextField *)textField
{

    [textField setSecureTextEntry:self.isUsingBullets];
    [textField setBackgroundColor:[UIColor blackColor]];
    [textField setTextColor:[UIColor blackColor]];
    [textField setBorderStyle:UITextBorderStyleNone];
    [textField setClipsToBounds:YES];

    [textField.layer setBorderColor:[[UIColor blackColor] CGColor]];
    [textField.layer setBorderWidth:1.0f];

    // make a gradient off-white background
    CAGradientLayer *gradient = [CAGradientLayer layer];
    CGRect gradRect = CGRectInset([textField bounds], 3, 3);    // Reduce Width and Height and center layer
    gradRect.size.height += 2;  // minimise Bottom shadow, rely on clipping to remove these 2 pts.

    gradient.frame = gradRect;
    struct CGColor *topColor = [UIColor colorWithWhite:0.6f alpha:1.0f].CGColor;
    struct CGColor *bottomColor = [UIColor colorWithWhite:0.9f alpha:1.0f].CGColor;
    // We need to use this fancy __bridge object in order to get the array we want.
    gradient.colors = [NSArray arrayWithObjects:(__bridge id)topColor, (__bridge id)bottomColor, nil];
    [gradient setCornerRadius:4.0f];
    [gradient setShadowOffset:CGSizeMake(0, 0)];
    [gradient setShadowColor:[[UIColor whiteColor] CGColor]];
    [gradient setShadowOpacity:1.0f];
    [gradient setShadowRadius:3.0f];

    // Now we need to Blur the edges of this layer "so it blends"
    // This rasterizes the view down to 4x4 pixel chunks then scales it back up using bilinear filtering...
    // it's EXTREMELY fast and looks ok if you are just wanting to blur a background view under a modal view.
    // To undo it, just set the rasterization scale back to 1.0 or turn off rasterization.
    [gradient setRasterizationScale:0.25];
    [gradient setShouldRasterize:YES];

    [textField.layer insertSublayer:gradient atIndex:0];

    if (self.usingAsterisks) {
        [textField setFont:[UIFont systemFontOfSize:80.0]];
    } else {
        [textField setFont:[UIFont systemFontOfSize:40.0]];
    }
    [textField setTextAlignment:UITextAlignmentCenter];
    [textField setEnabled:NO];
}

ฉันหวังว่านี่จะช่วยใครบางคนเนื่องจากฟอรัมนี้ช่วยฉันได้


3

ตรวจสอบบทความดีดีจากภายในเงาควอตซ์โดยคริส Emeryอธิบายว่าเงาด้านในจะมีการวาดโดยPaintCodeและให้ข้อมูลโค้ดสะอาดและเรียบร้อย:

- (void)drawInnerShadowInContext:(CGContextRef)context
                        withPath:(CGPathRef)path
                     shadowColor:(CGColorRef)shadowColor
                          offset:(CGSize)offset
                      blurRadius:(CGFloat)blurRadius 
{
    CGContextSaveGState(context);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);

    CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
    CGContextBeginTransparencyLayer(context, NULL);
        CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextSetFillColorWithColor(context, opaqueShadowColor);
        CGContextAddPath(context, path);
        CGContextFillPath(context);
    CGContextEndTransparencyLayer(context);

    CGContextRestoreGState(context);

    CGColorRelease(opaqueShadowColor);
}

3

นี่คือวิธีแก้ปัญหาของฉันใน Swift 4.2 คุณอยากลองไหม?

final class ACInnerShadowLayer : CAShapeLayer {

  var innerShadowColor: CGColor? = UIColor.black.cgColor {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOffset: CGSize = .zero {
    didSet { setNeedsDisplay() }
  }

  var innerShadowRadius: CGFloat = 8 {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOpacity: Float = 1 {
    didSet { setNeedsDisplay() }
  }

  override init() {
    super.init()

    masksToBounds = true
    contentsScale = UIScreen.main.scale

    setNeedsDisplay()
  }

  override init(layer: Any) {
      if let layer = layer as? InnerShadowLayer {
          innerShadowColor = layer.innerShadowColor
          innerShadowOffset = layer.innerShadowOffset
          innerShadowRadius = layer.innerShadowRadius
          innerShadowOpacity = layer.innerShadowOpacity
      }
      super.init(layer: layer)
  }

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

  override func draw(in ctx: CGContext) {
    ctx.setAllowsAntialiasing(true)
    ctx.setShouldAntialias(true)
    ctx.interpolationQuality = .high

    let colorspace = CGColorSpaceCreateDeviceRGB()

    var rect = bounds
    var radius = cornerRadius

    if borderWidth != 0 {
      rect = rect.insetBy(dx: borderWidth, dy: borderWidth)
      radius -= borderWidth
      radius = max(radius, 0)
    }

    let innerShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
    ctx.addPath(innerShadowPath)
    ctx.clip()

    let shadowPath = CGMutablePath()
    let shadowRect = rect.insetBy(dx: -rect.size.width, dy: -rect.size.width)
    shadowPath.addRect(shadowRect)
    shadowPath.addPath(innerShadowPath)
    shadowPath.closeSubpath()

    if let innerShadowColor = innerShadowColor, let oldComponents = innerShadowColor.components {
      var newComponets = Array<CGFloat>(repeating: 0, count: 4) // [0, 0, 0, 0] as [CGFloat]
      let numberOfComponents = innerShadowColor.numberOfComponents

      switch numberOfComponents {
      case 2:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[0]
        newComponets[2] = oldComponents[0]
        newComponets[3] = oldComponents[1] * CGFloat(innerShadowOpacity)
      case 4:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[1]
        newComponets[2] = oldComponents[2]
        newComponets[3] = oldComponents[3] * CGFloat(innerShadowOpacity)
      default:
        break
      }

      if let innerShadowColorWithMultipliedAlpha = CGColor(colorSpace: colorspace, components: newComponets) {
        ctx.setFillColor(innerShadowColorWithMultipliedAlpha)
        ctx.setShadow(offset: innerShadowOffset, blur: innerShadowRadius, color: innerShadowColorWithMultipliedAlpha)
        ctx.addPath(shadowPath)
        ctx.fillPath(using: .evenOdd)
      }
    } 
  }
}

จะเกิดอะไรขึ้นถ้าฉันไม่ได้ใช้มันเป็นคลาสแยกกัน แต่เช่นเดียวกับการใช้ในโค้ดของฉันบริบท (ctx) จะเป็นศูนย์เมื่อฉันได้รับสิ่งนี้:let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed คุณสามารถรับบริบทปัจจุบันโดยวิธีUIGraphicsGetCurrentContext การดึงข้อมูลเมื่อบางมุมมองดันบริบทของพวกเขาไปยังสแต็ก
Arco

@Arco ฉันมีปัญหาเมื่อหมุนอุปกรณ์ ฉันเพิ่ม 'override convenience init (layer: Any) {self.init ()}' ตอนนี้ไม่มีข้อผิดพลาดปรากฏ!
Yuma Technical Inc.

เพิ่ม init (ชั้น: ใด ๆ ) เพื่อแก้ไขข้อขัดข้อง
Nik Kov

2

โซลูชันที่ปรับขนาดได้โดยใช้ CALayer ใน Swift

ด้วยคำอธิบายInnerShadowLayerคุณยังสามารถเปิดใช้งานเงาภายในสำหรับขอบเฉพาะเท่านั้นไม่รวมส่วนอื่น ๆ (เช่นคุณสามารถเปิดใช้งานเงาภายในได้เฉพาะที่ขอบด้านซ้ายและด้านบนของมุมมองของคุณ)

จากนั้นคุณสามารถเพิ่ม a InnerShadowLayerในมุมมองของคุณโดยใช้:

init(...) {

    // ... your initialization code ...

    super.init(frame: .zero)
    layer.addSublayer(shadowLayer)
}

public override func layoutSubviews() {
    super.layoutSubviews()
    shadowLayer.frame = bounds
}

InnerShadowLayer การนำไปใช้งาน

/// Shadow is a struct defining the different kinds of shadows
public struct Shadow {
    let x: CGFloat
    let y: CGFloat
    let blur: CGFloat
    let opacity: CGFloat
    let color: UIColor
}

/// A layer that applies an inner shadow to the specified edges of either its path or its bounds
public class InnerShadowLayer: CALayer {
    private let shadow: Shadow
    private let edge: UIRectEdge

    public init(shadow: Shadow, edge: UIRectEdge) {
        self.shadow = shadow
        self.edge = edge
        super.init()
        setupShadow()
    }

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

    public override func layoutSublayers() {
        updateShadow()
    }

    private func setupShadow() {
        shadowColor = shadow.color.cgColor
        shadowOpacity = Float(shadow.opacity)
        shadowRadius = shadow.blur / 2.0
        masksToBounds = true
    }

    private func updateShadow() {
        shadowOffset = {
            let topWidth: CGFloat = 0
            let leftWidth = edge.contains(.left) ? shadow.y / 2 : 0
            let bottomWidth: CGFloat = 0
            let rightWidth = edge.contains(.right) ? -shadow.y / 2 : 0

            let topHeight = edge.contains(.top) ? shadow.y / 2 : 0
            let leftHeight: CGFloat = 0
            let bottomHeight = edge.contains(.bottom) ? -shadow.y / 2 : 0
            let rightHeight: CGFloat = 0

            return CGSize(width: [topWidth, leftWidth, bottomWidth, rightWidth].reduce(0, +),
                          height: [topHeight, leftHeight, bottomHeight, rightHeight].reduce(0, +))
        }()

        let insets = UIEdgeInsets(top: edge.contains(.top) ? -bounds.height : 0,
                                  left: edge.contains(.left) ? -bounds.width : 0,
                                  bottom: edge.contains(.bottom) ? -bounds.height : 0,
                                  right: edge.contains(.right) ? -bounds.width : 0)
        let path = UIBezierPath(rect: bounds.inset(by: insets))
        let cutout = UIBezierPath(rect: bounds).reversing()
        path.append(cutout)
        shadowPath = path.cgPath
    }
}


1

รหัสนี้ใช้ได้กับฉัน

class InnerDropShadowView: UIView {
    override func draw(_ rect: CGRect) {
        //Drawing code
        let context = UIGraphicsGetCurrentContext()
        //// Shadow Declarations
        let shadow: UIColor? = UIColor.init(hexString: "a3a3a3", alpha: 1.0) //UIColor.black.withAlphaComponent(0.6) //UIColor.init(hexString: "d7d7da", alpha: 1.0)
        let shadowOffset = CGSize(width: 0, height: 0)
        let shadowBlurRadius: CGFloat = 7.5
        //// Rectangle Drawing
        let rectanglePath = UIBezierPath(rect: bounds)
        UIColor.groupTableViewBackground.setFill()
        rectanglePath.fill()
        ////// Rectangle Inner Shadow
        context?.saveGState()
        UIRectClip(rectanglePath.bounds)
        context?.setShadow(offset: CGSize.zero, blur: 0, color: nil)
        context?.setAlpha((shadow?.cgColor.alpha)!)
        context?.beginTransparencyLayer(auxiliaryInfo: nil)
        do {
            let opaqueShadow: UIColor? = shadow?.withAlphaComponent(1)
            context?.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: opaqueShadow?.cgColor)
            context!.setBlendMode(.sourceOut)
            context?.beginTransparencyLayer(auxiliaryInfo: nil)
            opaqueShadow?.setFill()
            rectanglePath.fill()
            context!.endTransparencyLayer()
        }
        context!.endTransparencyLayer()
        context?.restoreGState()
    }
}

0

มีโค้ดบางอย่างที่สามารถทำเพื่อคุณได้ หากคุณเปลี่ยนเลเยอร์ในมุมมองของคุณ (โดยการลบล้าง+ (Class)layerClass) เป็น JTAInnerShadowLayer คุณสามารถตั้งค่าเงาภายในบนเลเยอร์การเยื้องขึ้นในวิธีการเริ่มต้นของคุณและมันจะทำงานให้คุณ หากคุณต้องการวาดเนื้อหาต้นฉบับด้วยอย่าลืมเรียกsetDrawOriginalImage:yesเลเยอร์เยื้อง มีการโพสต์บล็อกเกี่ยวกับวิธีการทำงานนี้เป็นที่นี่


@MiteshDobareeya เพิ่งทดสอบทั้งสองลิงก์และดูเหมือนว่าจะใช้งานได้ดี (รวมถึงในแท็บส่วนตัว) ลิงก์ใดที่ทำให้คุณเกิดปัญหา
James Snook

คุณช่วยดูการใช้งานโค้ดเงาภายในนี้ได้ไหม มันทำงานในเมธอด ViewDidAppear เท่านั้น และแสดงการกะพริบ drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya

0

ใช้ Gradient Layer:

UIView * mapCover = [UIView new];
mapCover.frame = map.frame;
[view addSubview:mapCover];

CAGradientLayer * vertical = [CAGradientLayer layer];
vertical.frame = mapCover.bounds;
vertical.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[UIColor whiteColor].CGColor, nil];
vertical.locations = @[@0.01,@0.1,@0.9,@0.99];
[mapCover.layer insertSublayer:vertical atIndex:0];

CAGradientLayer * horizontal = [CAGradientLayer layer];
horizontal.frame = mapCover.bounds;
horizontal.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[UIColor whiteColor].CGColor, nil];
horizontal.locations = @[@0.01,@0.1,@0.9,@0.99];
horizontal.startPoint = CGPointMake(0.0, 0.5);
horizontal.endPoint = CGPointMake(1.0, 0.5);
[mapCover.layer insertSublayer:horizontal atIndex:0];

0

มีวิธีง่ายๆเพียงวาดเงาปกติแล้วหมุนตามนี้

@objc func shadowView() -> UIView {
        let shadowView = UIView(frame: .zero)
        shadowView.backgroundColor = .white
        shadowView.layer.shadowColor = UIColor.grey.cgColor
        shadowView.layer.shadowOffset = CGSize(width: 0, height: 2)
        shadowView.layer.shadowOpacity = 1.0
        shadowView.layer.shadowRadius = 4
        shadowView.layer.compositingFilter = "multiplyBlendMode"
        return shadowView
    }

func idtm_addBottomShadow() {
        let shadow = shadowView()
        shadow.transform = transform.rotated(by: 180 * CGFloat(Double.pi))
        shadow.transform = transform.rotated(by: -1 * CGFloat(Double.pi))
        shadow.translatesAutoresizingMaskIntoConstraints = false
        addSubview(shadow)
        NSLayoutConstraint.activate([
            shadow.leadingAnchor.constraint(equalTo: leadingAnchor),
            shadow.trailingAnchor.constraint(equalTo: trailingAnchor),
            shadow.bottomAnchor.constraint(equalTo: bottomAnchor),
            shadow.heightAnchor.constraint(equalToConstant: 1),
            ])
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.