Эффект внутренней тени на слое UIView?
У меня есть следующий 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, поскольку он должен быть баром за некоторыми кнопками, поэтому я не знаю, что делать?
Я мог бы добавить еще один слой, но опять же, не уверен, как достичь внутренней теневой эффект (как это:

помощь оценили...
15 ответов:
для тех, кто еще задается вопросом, Как нарисовать внутреннюю тень с помощью основной графики в соответствии с предложением 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);Итак, по существу есть следующие шаги:
- создать свой путь
- установите нужный цвет заливки, добавьте этот путь в контекст и заполните контекст
- Теперь создайте больший прямоугольник, который может связать видимый путь. Перед закрытием этого пути добавьте видимый путь. Затем закройте путь, чтобы создать фигуру с вычитаемым из нее видимым путем. Вы можете исследовать методы заполнения (ненулевая обмотка четного/нечетного) в зависимости от того, как вы создали эти пути. По сути, чтобы заставить подпути "вычитать" при сложении их вместе, вам нужно нарисовать их (или, скорее, построить их) в противоположных направлениях, один по часовой стрелке, а другой против часовой стрелки.
- затем вам нужно установить видимый путь как путь отсечения в контексте, так что вы не рисуете ничего вне его на экране.
- затем установите тень на контекст, который включает в себя смещение, размытие и цвет.
Заполните большую форму с отверстием в нем. Цвет не имеет значения, потому что если вы все сделали правильно, вы не увидите этот цвет, только тень.
Я знаю, что опаздываю на эту вечеринку, но это помогло бы мне найти рано в моих путешествиях...
чтобы дать кредит, где кредит должен, это по существу модификация разработки Даниэля Торпа по решению Костика вычитания меньшей области из большей области. Эта версия предназначена для тех, кто использует композицию слоя вместо переопределения
-drawRect:The
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];Я не сравнивал это сам, но я подозреваю, что использование этого подхода в сочетании с растеризацией более эффективно, чем переопределение
-drawRect:.
можно нарисовать внутреннюю тень с основной графикой, сделав большой путь прямоугольника за пределами границ, вычитая путь прямоугольника размером с границы и заполняя полученный путь "нормальной" тенью.
однако, поскольку вам нужно объединить его с градиентным слоем, я думаю, что более простым решением является создание прозрачного изображения 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, и она должна правильно растягивать тень.
немного круговой способ, но он позволяет избежать использования изображений (читай: легко менять цвета, радиус тени и т. д.) и это всего лишь несколько строк кода.
добавьте UIImageView в качестве первого подвида UIView, на котором вы хотите использовать dropshadow. Я использую IB, но вы можете сделать то же самое программно.
предполагая, что ссылка на 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макрос.
упрощенная версия с использованием только 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 не должен иметь непрозрачный цвет фона, так как он будет отображаться перед тенью.
лучше поздно, чем никогда...
вот еще один подход, вероятно, не лучше, чем те, которые уже опубликованы, но это приятно и просто -
-(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]; }
вместо рисования внутренней тени с помощью drawRect или добавить UIView к виду. Вы можете напрямую добавить 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
здесь-это версия быстрого, изменения
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)
Это ваше решение, которое я экспортировал из 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); }
Я очень опаздываю на вечеринку, но я хотел бы вернуть сообщество.. Это метод, который я написал, чтобы удалить фоновое изображение UITextField, поскольку я предоставлял статическую библиотеку и никаких ресурсов... Я использовал это для экрана ввода 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]; }Я надеюсь, что это помогает кто-то, как этот форум помог мне.
Проверьте большую статью внутренние тени в кварце by Крис Эмери wich объясняется как внутренние тени рисуются 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); }
вот мое решение в Swift 4.2. Вы бы хотели попробовать?
final class ACInnerShadowLayer : CAShapeLayer { // MARK: Public 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() } } // MARK: Initial override init() { super.init() masksToBounds = true contentsScale = UIScreen.main.scale setNeedsDisplay() } 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) } } }}
есть какой-то код здесь, который может сделать это для вас. Если вы измените слой в своем представлении (путем переопределения
+ (Class)layerClass), чтобы JTAInnerShadowLayer, то вы можете установить внутреннюю тень на слой отступа в вашем методе init, и он будет делать работу за вас. Если вы также хотите нарисовать исходный контент, убедитесь, что вы звонитеsetDrawOriginalImage:yesна слое отступа. Есть в блоге о том, как это работает здесь.
Использование Градиентного Слоя:
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];
этот код работал для меня
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() } }

Comments