UIImagePickerController editing view circle overlay - ios

I've been able to get pretty far with what I've been wanting to accomplish, and that's to replicate iOS's built in circular photo cropper for the built in contacts app. However, I'm stuck at trying to get my CAShapeLayers made correctly. I'm trying to make a transparent 320 px diameter circle and the rest of the view filled with an 0.9 alpha black background. The circle and rectangle are in the right place, but, the circle is not completely transparent like I need it to be.
I'm lost as to how to fix this. I appreciate your help! Code and screenshot:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController.viewControllers count] == 3)
{
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenHeight = screenRect.size.height;
UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];
plCropOverlay.hidden = YES;
CAShapeLayer *circleLayer = [CAShapeLayer layer];
if (screenHeight == 568)
{
[circleLayer setPosition:CGPointMake(0.0f,124.0f)];
}
else
{
[circleLayer setPosition:CGPointMake(0.0f,80.0f)];
}
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, 0.0f, 320.0f, 320.0f)];
[circleLayer setPath:[path CGPath]];
[circleLayer setFillColor:[[UIColor whiteColor] CGColor]];
circleLayer.opacity = 0.7f;
// Set to 0.7f to show for screenshot purposes; setting to 0.0 would make it invisible and blend in with the below rectangleLayer.
CAShapeLayer *rectangleLayer = [CAShapeLayer layer];
UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:CGRectMake(0.0f, 0.0f, 320.0f, screenHeight - 72)];
[rectangleLayer setPath:[path2 CGPath]];
[rectangleLayer setFillColor:[[UIColor blackColor] CGColor]];
[rectangleLayer setOpacity:0.9f];
[rectangleLayer addSublayer:circleLayer];
[[viewController.view layer] addSublayer:rectangleLayer];
UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
[moveLabel setText:#"Move and Scale"];
[moveLabel setTextAlignment:NSTextAlignmentCenter];
[moveLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:moveLabel];
}
}

Resolved code:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController.viewControllers count] == 3)
{
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
UIView *plCropOverlay = [[[viewController.view.subviews objectAtIndex:1]subviews] objectAtIndex:0];
plCropOverlay.hidden = YES;
int position = 0;
if (screenHeight == 568)
{
position = 124;
}
else
{
position = 80;
}
CAShapeLayer *circleLayer = [CAShapeLayer layer];
UIBezierPath *path2 = [UIBezierPath bezierPathWithOvalInRect:
CGRectMake(0.0f, position, 320.0f, 320.0f)];
[path2 setUsesEvenOddFillRule:YES];
[circleLayer setPath:[path2 CGPath]];
[circleLayer setFillColor:[[UIColor clearColor] CGColor]];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 320, screenHeight-72) cornerRadius:0];
[path appendPath:path2];
[path setUsesEvenOddFillRule:YES];
CAShapeLayer *fillLayer = [CAShapeLayer layer];
fillLayer.path = path.CGPath;
fillLayer.fillRule = kCAFillRuleEvenOdd;
fillLayer.fillColor = [UIColor blackColor].CGColor;
fillLayer.opacity = 0.8;
[viewController.view.layer addSublayer:fillLayer];
UILabel *moveLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, 320, 50)];
[moveLabel setText:#"Move and Scale"];
[moveLabel setTextAlignment:NSTextAlignmentCenter];
[moveLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:moveLabel];
}
}

I've changed the code of #aviatorken89 because it wasn't working on iPhone 6/6+ and iPad. Now it should work with any iPhone's screen size and also on iPad! Tested on iOS 7 and iOS 8.
All these methods aren't really reliable because they are based on the Image Picker subviews hierarchy, and of course Apple could change it. I've tried to protect the code as far as I could, in order to prevent possibile crashes on future iOS releases.
I'll try to keep my solution updated on a gist: https://gist.github.com/andreacipriani/74ea67db8f17673f1b8b
Here is the code:
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([navigationController.viewControllers count] == 3 && ([[[[navigationController.viewControllers objectAtIndex:2] class] description] isEqualToString:#"PUUIImageViewController"] || [[[[navigationController.viewControllers objectAtIndex:2] class] description] isEqualToString:#"PLUIImageViewController"]))
[self addCircleOverlayToImagePicker:viewController];
}
}
-(void)addCircleOverlayToImagePicker:(UIViewController*)viewController
{
UIColor *circleColor = [UIColor clearColor];
UIColor *maskColor = [[UIColor blackColor] colorWithAlphaComponent:0.8];
CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
UIView *plCropOverlayCropView; //The default crop overlay view, we wan't to hide it and show our circular one
UIView *plCropOverlayBottomBar; //On iPhone this is the bar with "cancel" and "choose" buttons, on Ipad it's an Image View with a label saying "Scale and move"
//Subviews hirearchy is different in iPad/iPhone:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
plCropOverlayCropView = [viewController.view.subviews objectAtIndex:1];
plCropOverlayBottomBar = [[[[viewController.view subviews] objectAtIndex:1] subviews] objectAtIndex:1];
//Protect against iOS changes...
if (! [[[plCropOverlayCropView class] description] isEqualToString:#"PLCropOverlay"]){
DLog(#"Warning - Image Picker with circle overlay: PLCropOverlay not found");
return;
}
if (! [[[plCropOverlayBottomBar class] description] isEqualToString:#"UIImageView"]){
DLog(#"Warning - Image Picker with circle overlay: BottomBar not found");
return;
}
}
else{
plCropOverlayCropView = [[[viewController.view.subviews objectAtIndex:1] subviews] firstObject];
plCropOverlayBottomBar = [[[[viewController.view subviews] objectAtIndex:1] subviews] objectAtIndex:1];
//Protect against iOS changes...
if (! [[[plCropOverlayCropView class] description] isEqualToString:#"PLCropOverlayCropView"]){
DDLogWarn(#"Image Picker with circle overlay: PLCropOverlayCropView not found");
return;
}
if (! [[[plCropOverlayBottomBar class] description] isEqualToString:#"PLCropOverlayBottomBar"]){
DDLogWarn(#"Image Picker with circle overlay: PLCropOverlayBottomBar not found");
return;
}
}
//It seems that everything is ok, we found the CropOverlayCropView and the CropOverlayBottomBar
plCropOverlayCropView.hidden = YES; //Hide default CropView
CAShapeLayer *circleLayer = [CAShapeLayer layer];
//Center the circleLayer frame:
UIBezierPath *circlePath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0.0f, screenHeight/2 - screenWidth/2, screenWidth, screenWidth)];
circlePath.usesEvenOddFillRule = YES;
circleLayer.path = [circlePath CGPath];
circleLayer.fillColor = circleColor.CGColor;
//Mask layer frame: it begins on y=0 and ends on y = plCropOverlayBottomBar.origin.y
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, screenWidth, screenHeight- plCropOverlayBottomBar.frame.size.height) cornerRadius:0];
[maskPath appendPath:circlePath];
maskPath.usesEvenOddFillRule = YES;
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskPath.CGPath;
maskLayer.fillRule = kCAFillRuleEvenOdd;
maskLayer.fillColor = maskColor.CGColor;
[viewController.view.layer addSublayer:maskLayer];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone){
//On iPhone add an hint label on top saying "scale and move" or whatever you want
UILabel *cropLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 10, screenWidth, 50)];
[cropLabel setText:#"Scale and move"]; //You should localize it
[cropLabel setTextAlignment:NSTextAlignmentCenter];
[cropLabel setTextColor:[UIColor whiteColor]];
[viewController.view addSubview:cropLabel];
}
else{ //On iPad re-add the overlayBottomBar with the label "scale and move" because we set its parent to hidden (it's a subview of PLCropOverlay)
[viewController.view addSubview:plCropOverlayBottomBar];
}
}

Swift 3 version (also with rounded edit layer for pictures taken by camera):
// Insert this code to your view controller
private var editLayer: CAShapeLayer!
private var label: UILabel!
override func viewDidLoad()
{
super.viewDidLoad()
// Rounded edit layer
navigationController?.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(pictureCaptured), name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidCaptureItem"), object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(pictureRejected), name: NSNotification.Name(rawValue: "_UIImagePickerControllerUserDidRejectItem"), object: nil)
}
#objc private func pictureCaptured()
{
addRoundedEditLayer(to: ...your UIImagePickerController..., forCamera: true)
}
#objc private func pictureRejected()
{
editLayer.removeFromSuperlayer()
label.removeFromSuperview()
}
deinit
{
NotificationCenter.default.removeObserver(self)
}
// MARK: Navigation controller delegate
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)
{
// Image picker in edit mode
if let imageVC = NSClassFromString("PUUIImageViewController")
{
if viewController.isKind(of: imageVC) {
addRoundedEditLayer(to: viewController, forCamera: false)
}
}
}
private func addRoundedEditLayer(to viewController: UIViewController, forCamera: Bool)
{
hideDefaultEditOverlay(view: viewController.view)
// Circle in edit layer - y position
let bottomBarHeight: CGFloat = 72.0
let position = (forCamera) ? viewController.view.center.y - viewController.view.center.x - bottomBarHeight/2 : viewController.view.center.y - viewController.view.center.x
let viewWidth = viewController.view.frame.width
let viewHeight = viewController.view.frame.height
let emptyShapePath = UIBezierPath(ovalIn: CGRect(x: 0, y: position, width: viewWidth, height: viewWidth))
emptyShapePath.usesEvenOddFillRule = true
let filledShapePath = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight - bottomBarHeight), cornerRadius: 0)
filledShapePath.append(emptyShapePath)
filledShapePath.usesEvenOddFillRule = true
editLayer = CAShapeLayer()
editLayer.path = filledShapePath.cgPath
editLayer.fillRule = kCAFillRuleEvenOdd
editLayer.fillColor = UIColor.black.cgColor
editLayer.opacity = 0.5
viewController.view.layer.addSublayer(editLayer)
// Move and Scale label
label = UILabel(frame: CGRect(x: 0, y: 10, width: viewWidth, height: 50))
label.text = "Move and Scale"
label.textAlignment = .center
label.textColor = UIColor.white
viewController.view.addSubview(label)
}
private func hideDefaultEditOverlay(view: UIView)
{
for subview in view.subviews
{
if let cropOverlay = NSClassFromString("PLCropOverlayCropView")
{
if subview.isKind(of: cropOverlay) {
subview.isHidden = true
break
}
else {
hideDefaultEditOverlay(view: subview)
}
}
}
}

For doing this from camera, try using the cameraOverlayView and set your own view. That will work only when picking from camera though and not photo library.

In the code of Jakub Marek, there's an issue with persistent rounded layer if you open a second time the camera.
to solve it add in your openCamera func:
editLayer?.removeFromSuperlayer()
label?.removeFromSuperview()
and replace in private func hideDefaultEditOverlay(view: UIView)
subview.isHidden = true
by
subview.removeFromSuperview()
Code :
func openCamera(){
if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerController.SourceType.camera)){
imagePicker.sourceType = UIImagePickerController.SourceType.camera
//If you dont want to edit the photo then you can set allowsEditing to false
imagePicker.allowsEditing = true
imagePicker.cameraDevice = .rear
imagePicker.showsCameraControls = true
imagePicker.cameraCaptureMode = .photo
imagePicker.delegate = self
editLayer?.removeFromSuperlayer()
label?.removeFromSuperview()
self.present(imagePicker, animated: true, completion: nil)
}
else{
let alert = UIAlertController(title: NSLocalizedString("Attention",comment:""), message: NSLocalizedString("You don't have any camera",comment:""), preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK",comment:""), style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
private func hideDefaultEditOverlay(view: UIView)
{
for subview in view.subviews
{
if let cropOverlay = NSClassFromString("PLCropOverlayCropView")
{
if subview.isKind(of: cropOverlay) {
subview.removeFromSuperview()
//subview.isHidden = true
break
}
else {
hideDefaultEditOverlay(view: subview)
}
}
}
}

Related

Create gradient-based UICollectionViewCell background colour

I want to create custom bar for displaying some progress, that must look something like this:
So, as you can see each bar has it's own gradient background. I want to use UICollectionView for this. But problem is to create gradient background for each individual cell. So is it possible somehow create ONE basic gradient for whole UICollectionView, and then mask it, to be visible inside UICollectionViewCell?
Okay, here is the example, background is the gradient layer with different colours are spread and above you can create a mask layer to appear only on the rectangular shapes, which gives the illusion of the different rectangular layers having different gradient, you can play with gradient to get your desired effect,
First you subclass the UIView or you can create directly, better you can create a subclass as I am doing in the example
obj-c
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self createGreadient];
}
return self;
}
//this create a grenadine with the rectangular mask layer
- (void)createGreadient
{
UIColor *theColor = [UIColor redColor];//[[UIColor alloc] initWithRed:146.0/255.0 green:146.0/255.0 blue:146.0/255.0 alpha:1];
CAGradientLayer *gradientLayer = [[CAGradientLayer alloc] init];
gradientLayer.frame = self.bounds;
//u can add your own colours to get your requirement
gradientLayer.colors = [NSArray arrayWithObjects:
(id)[theColor CGColor],
(id)[[theColor colorWithAlphaComponent:0.7f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.4f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.3f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.2f] CGColor],
(id)[[theColor colorWithAlphaComponent:0.1f] CGColor],
(id)[[UIColor clearColor] CGColor],nil];
[self.layer addSublayer:gradientLayer];
CAShapeLayer *layerMask = [[CAShapeLayer alloc] init];
layerMask.frame = self.bounds;
UIBezierPath *rectangularMaskPath = [self getRectangularMaskPathForCount:5];
layerMask.path = rectangularMaskPath.CGPath;
gradientLayer.mask = layerMask;
}
//this creates rectangular path, u can adjust the rectangular and u can
//pass different number of count as the progress proceeds
- (UIBezierPath *)getRectangularMaskPathForCount:(NSInteger)rectCount
{
UIBezierPath *path = [[UIBezierPath alloc] init];
int i = 0;
for(;i <= rectCount; i++)
{
UIBezierPath *aPath;
CGRect aRect = CGRectMake(20+ (i * 20), 20, 10, 100); //set the rectangular position to your requirement
aPath = [UIBezierPath bezierPathWithRect:aRect];
[path appendPath:aPath];
}
return path;
}
swift version
subclass the UIView and past the below code,
override init (frame : CGRect) {
super.init(frame : frame)
createGreadient()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createGreadient()
{
let color:UIColor = UIColor.red
let greadetLayer = CAGradientLayer.init()
greadetLayer.frame = self.bounds
greadetLayer.colors = [color.withAlphaComponent(0.8).cgColor,color.withAlphaComponent(0.7),color.withAlphaComponent(0.4).cgColor,color.withAlphaComponent(0.3).cgColor,color.withAlphaComponent(0.2),color.withAlphaComponent(0.1).cgColor]
self.layer .addSublayer(greadetLayer)
let rectangularMaskPath = self.creatrRectangularPathWithCount(countRect: 5)
let maskLayer:CAShapeLayer = CAShapeLayer()
maskLayer.frame = self.bounds
maskLayer.path = rectangularMaskPath.cgPath
greadetLayer.mask = maskLayer
}
func creatrRectangularPathWithCount(countRect:NSInteger) -> UIBezierPath {
let path : UIBezierPath = UIBezierPath()
for i in 0..<countRect {
let aPath = UIBezierPath.init(rect: CGRect(x: 20 + (i * 20), y:20, width: 10, height: 100))
path.append(aPath)
}
return path
}
You can use the above view like below,
in ViewController
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let customView:CustomView = CustomView(frame: CGRect(x: 20, y: 20, width: 300, height: 300))
self.view.addSubview(customView)
}

How to add label or text in to CAShapeLayer

Here is my class and it'll draw a circle, it looks like this:
class OvalLayer: CAShapeLayer {
let animationDuration: CFTimeInterval = 0.3
override init() {
super.init()
fillColor = Colors.green.CGColor
path = ovalPathSmall.CGPath
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var ovalPathStart: UIBezierPath {
let path = UIBezierPath(ovalInRect: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
return path
}
}
Now I need to add a text to middle of this circle, I tried to find it on google but nothing that works fine. I am not sure if it's possible or not, can anyone help me if it's possible?
I guess you should add CATextLayer as a sublayer to CALayer... That works fine that way: try adding CAShapeLayer first, and then CATextLayer (to same CALayer parent layer), for example in following order...
// assume self - is UIView instance
self.layer.addSublayer(shapedLayer) // shapedLayer - CAShapeLayer instance
self.layer.addSublayer(textLayer) // textLayer - CATextLayer instance
Swift 3.0
let label = UILabel()
label.font = UIFont(name: "Helvetica-Bold", size: 12)
label.frame = CGRect(x: OvalLayer.frame.origin.x + (circleWidth/2), y: OvalLayer.frame.origin.y, width: OvalLayer.bounds.width, height: OvalLayer.bounds.height)
label.text = "Hello"
label.textColor = UIColor.red
label.isHidden = false
OvalLayer.addSublayer(label.layer)
You just need to get the center of your UIBezierPath and add a label or a CATextLayer to your current layer.
let center : CGPoint = CGPoint(x: CGRectGetMidX(ovalPathStart.bounds), y: CGRectGetMidX(ovalPathStart.bounds))
Now, create a UILabel or CATextLayer and set the center.
Text on CAShapeLayer Using CATextLayer
Here i am create CAShapeLayer object and i am adding CATextLayer to CAShapeLayer
Here numberOfArcsCount means some 8
CAShapeLayer *progressLayer = [[CAShapeLayer alloc] init];
[progressLayer setPath:bezierPath.CGPath];
CATextLayer* text = [CATextLayer new];
for (int i = 1; i <= numberOfArcs.count; i++) {
text.string = [NSString stringWithFormat:#"%i", i];
text.font = (__bridge CFTypeRef _Nullable)([UIFont fontWithName:#"akaChen" size:42]);
text.font = (__bridge CFTypeRef _Nullable)([UIFont boldSystemFontOfSize:15]);
text.fontSize=25;
text.frame = CGRectMake(0,0,40,40);
text.position = CGPointMake(CGRectGetMidX(progressLayer.frame) ,CGRectGetMidY(progressLayer.frame) );
CGFloat vert = CGRectGetMidY(progressLayer.frame) / CGRectGetHeight(text.frame);
text.anchorPoint = CGPointMake(0.5, vert );
text.alignmentMode = kCAAlignmentCenter;
text.foregroundColor = [[UIColor whiteColor] CGColor];
[progressLayer addSublayer:text];
}
This way to add label to layer :
labelFrame - CGRect
someString - String
layer - your layer
let label = CATextLayer()
label.frame = labelFarme
label.string = someString
layer.addSublayer(label)

UISearchBar scope bar background image settable, but not its color?

for subView in searchBar.subviews {
if let scopeBar = subView as? UISegmentedControl {
scopeBar.backgroundColor = UIColor.blueColor()
}
}
I've been trying the above code to attempt to get a reference to the scopeBar and subsequently set its background color, but I am not able to get a reference. It seems to only go through the loop once, implying that there is only a single subview for the search bar. In the debugger the search bar appears to have an instance variable called _scopeBar with a type of (UISegmentedControl *).
if let topView = searchBar.subviews.first {
for subView in topView.subviews {
if let cancelButton = subView as? UIButton {
cancelButton.tintColor = UIColor.whiteColor()
cancelButton.enabled = true
}
}
}
The second block of code works for accessing the cancelButton of the search bar.
You can set the scopeBar background image as a solid color image.
First, you will need to create an UIImage from a color. In order to do that, you can create a function, or an extension on the UIImage, such as the following code:
Swift 3
import UIKit
extension UIImage {
class func imageWithColor(color: UIColor) -> UIImage {
let rect: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), false, 0)
color.setFill()
UIRectFill(rect)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
After that, you can simply set the backgroundImage on the scope bar:
Swift 3
// Set the scopeBar's background color:
searchBar.scopeBarBackgroundImage = UIImage.imageWithColor(color: UIColor.blue)
I have a sample issue, and I solved this as below code.
CGSize imageSize = CGSizeMake(64, 64);
UIColor *fillColor = [UIColor colorWithRed:228/255.0 green:228/255.0 blue:228/255.0 alpha:1.0];
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
[fillColor setFill];
CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.searchController.searchBar.scopeBarBackgroundImage = image;
- (void)changeScopeBarColor {
NSMutableArray<UIView*> *views = [NSMutableArray new];
NSArray<UIView *> *subviews = [_searchBar subviews];
[views addObjectsFromArray:subviews];
for (;;) {
if (views.count == 0) {
break;
}
UIView *v = [views firstObject];
[views removeObject:v];
if ([[[v class] description] isEqualToString:#"_UISearchBarScopeBarBackground"]) {
v.backgroundColor = [UIColor whiteColor];
break;
}
if (v.subviews.count > 0)
[views addObjectsFromArray:v.subviews];
}
}

How to change inside background color of UISearchBar component on iOS

I know how to remove/change UISearchBar background color around search field:
[[self.searchBar.subviews objectAtIndex:0] removeFromSuperview];
self.searchBar.backgroundColor = [UIColor grayColor];
But don't know how to do this inside it like that:
This needs to be compatible with iOS 4.3+.
Just customize the text field itself.
I am simply doing this and it works fine for me (iOS 7).
UITextField *txfSearchField = [_searchBar valueForKey:#"_searchField"];
txfSearchField.backgroundColor = [UIColor redColor];
This way you don't need to create an image, size it, etc...
Details
Xcode Version 11.0 (11A420a), swift 5
UISearchBar customising sample
Solution
import UIKit
extension UISearchBar {
func getTextField() -> UITextField? { return value(forKey: "searchField") as? UITextField }
func setTextField(color: UIColor) {
guard let textField = getTextField() else { return }
switch searchBarStyle {
case .minimal:
textField.layer.backgroundColor = color.cgColor
textField.layer.cornerRadius = 6
case .prominent, .default: textField.backgroundColor = color
#unknown default: break
}
}
}
Usage
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 20, width: UIScreen.main.bounds.width, height: 44))
//searchBar.searchBarStyle = .prominent
view.addSubview(searchBar)
searchBar.placeholder = "placeholder"
searchBar.setTextField(color: UIColor.green.withAlphaComponent(0.3))
Result 1
searchBar.searchBarStyle = .prominent // or default
Result 2
searchBar.searchBarStyle = .minimal
Full sample
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let searchBar = UISearchBar(frame: CGRect(x: 0, y: 20, width: UIScreen.main.bounds.width, height: 44))
//searchBar.searchBarStyle = .minimal
searchBar.searchBarStyle = .prominent
view.addSubview(searchBar)
searchBar.placeholder = "placeholder"
searchBar.setTextField(color: UIColor.green.withAlphaComponent(0.3))
}
}
Solution which doesn't involve any private API ! :)
Currently (probably since iOS 5 ) you can do this, for simply one colour cases, in this way:
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setBackgroundColor:[UIColor redColor]];
but please keep in mind that as it basis on appearance the change will be global for the app (it can be an advantage or a disadvantage of the solution).
For Swift you can use (it will work for iOS 9 and above):
if #available(iOS 9.0, *) {
UITextField.appearanceWhenContainedInInstancesOfClasses([UISearchBar.self]).backgroundColor = UIColor.darkGrayColor()
}
You do not need #available if your project supports iOS 9 and newer.
If you need to support earlier versions of iOS and want to use Swift take a look at this question.
Use this code to change the searchBar's UITextField backgroundImage:
UITextField *searchField;
NSUInteger numViews = [searchBar.subviews count];
for (int i = 0; i < numViews; i++) {
if ([[searchBar.subviews objectAtIndex:i] isKindOfClass:[UITextField class]]) { //conform?
searchField = [searchBar.subviews objectAtIndex:i];
}
}
if (searchField) {
searchField.textColor = [UIColor whiteColor];
[searchField setBackground: [UIImage imageNamed:#"yourImage"]]; //set your gray background image here
[searchField setBorderStyle:UITextBorderStyleNone];
}
Use the below code to change the UISearchBarIcon:
UIImageView *searchIcon = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"yourSearchBarIconImage"]];
searchIcon.frame = CGRectMake(10, 10, 24, 24);
[searchBar addSubview:searchIcon];
[searchIcon release];
Also, to change the searchBar icon you can use the following built-in method on UISearchBar (which is available from iOS 5+):
- (void)setImage:(UIImage *)iconImage forSearchBarIcon:(UISearchBarIcon)icon state:(UIControlState)state
Here you can set 4 types of UISearchBarIcon i.e.:
UISearchBarIconBookmark
UISearchBarIconClear
UISearchBarIconResultsList
UISearchBarIconSearch
I hope this help you...
According to the UISearchBar documentation:
You should use this function for iOS 5.0+.
- (void)setSearchFieldBackgroundImage:(UIImage *)backgroundImage forState:(UIControlState)state
Usage example:
[mySearchBar setSearchFieldBackgroundImage:myImage forState:UIControlStateNormal];
Sadly, in iOS 4 you need to revert to less sophisticated methods. See other answers.
As Accatyyc says for iOS5+ use setSearchFieldBackgroundImage, but you either need to create a graphic, or do the following:
CGSize size = CGSizeMake(30, 30);
// create context with transparent background
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
// Add a clip before drawing anything, in the shape of an rounded rect
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0,0,30,30)
cornerRadius:5.0] addClip];
[[UIColor grayColor] setFill];
UIRectFill(CGRectMake(0, 0, size.width, size.height));
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.searchBar setSearchFieldBackgroundImage:image forState:UIControlStateNormal];
what about apple way?
UISearchBar.appearance().setSearchFieldBackgroundImage(myImage, for: .normal)
you can set any image on your design!
But if you want create all programmaticle, you ca do this
my solution on Swift 3
let searchFieldBackgroundImage = UIImage(color: .searchBarBackground, size: CGSize(width: 44, height: 30))?.withRoundCorners(4)
UISearchBar.appearance().setSearchFieldBackgroundImage(searchFieldBackgroundImage, for: .normal)
where i use helpers extension
public extension UIImage {
public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
let rect = CGRect(origin: .zero, size: size)
UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
color.setFill()
UIRectFill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard let cgImage = image?.cgImage else { return nil }
self.init(cgImage: cgImage)
}
public func withRoundCorners(_ cornerRadius: CGFloat) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, false, scale)
let rect = CGRect(origin: CGPoint.zero, size: size)
let context = UIGraphicsGetCurrentContext()
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
context?.beginPath()
context?.addPath(path.cgPath)
context?.closePath()
context?.clip()
draw(at: CGPoint.zero)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
return image;
}
}
To do this on iOS 13+,
searchController.searchBar.searchTextField.backgroundColor = // your color here
Do note that by default the searchTextField.borderStyle is set to roundedRect, which applies a slight gray overlay on top of the color that you will set. If this is undesired, do
searchController.searchBar.searchTextField.borderStyle = .none
This will get rid of the gray overlay, but also get rid of the rounded corners.
I've found this to be the best way to customize the appearance of various search bar attributes in Swift 2.2 and iOS 8+ using UISearchBarStyle.Minimal
searchBar = UISearchBar(frame: CGRectZero)
searchBar.tintColor = UIColor.whiteColor() // color of bar button items
searchBar.barTintColor = UIColor.fadedBlueColor() // color of text field background
searchBar.backgroundColor = UIColor.clearColor() // color of box surrounding text field
searchBar.searchBarStyle = UISearchBarStyle.Minimal
// Edit search field properties
if let searchField = searchBar.valueForKey("_searchField") as? UITextField {
if searchField.respondsToSelector(Selector("setAttributedPlaceholder:")) {
let placeholder = "Search"
let attributedString = NSMutableAttributedString(string: placeholder)
let range = NSRange(location: 0, length: placeholder.characters.count)
let color = UIColor(white: 1.0, alpha: 0.7)
attributedString.addAttribute(NSForegroundColorAttributeName, value: color, range: range)
attributedString.addAttribute(NSFontAttributeName, value: UIFont(name: "AvenirNext-Medium", size: 15)!, range: range)
searchField.attributedPlaceholder = attributedString
searchField.clearButtonMode = UITextFieldViewMode.WhileEditing
searchField.textColor = .whiteColor()
}
}
// Set Search Icon
let searchIcon = UIImage(named: "search-bar-icon")
searchBar.setImage(searchIcon, forSearchBarIcon: .Search, state: .Normal)
// Set Clear Icon
let clearIcon = UIImage(named: "clear-icon")
searchBar.setImage(clearIcon, forSearchBarIcon: .Clear, state: .Normal)
// Add to nav bar
searchBar.sizeToFit()
navigationItem.titleView = searchBar
without using private API's:
for (UIView* subview in [[self.searchBar.subviews lastObject] subviews]) {
if ([subview isKindOfClass:[UITextField class]]) {
UITextField *textField = (UITextField*)subview;
[textField setBackgroundColor:[UIColor redColor]];
}
}
For Changing Only Color :
searchBar.tintColor = [UIColor redColor];
For Applying Background Image :
[self.searchBar setSearchFieldBackgroundImage:
[UIImage imageNamed:#"Searchbox.png"]
forState:UIControlStateNormal];
A better solution is to set the appearance of the UITextField inside UISearchBar
[[UITextField appearanceWhenContainedIn:[UISearchBar class], nil] setBackgroundColor:[UIColor grayColor]];
try this in iOS13 in swift
#IBOutlet weak var searchBar: UISearchBar!
searchBar.barTintColor = .systemIndigo
searchBar.searchTextField.backgroundColor = .white
Just traverse all views using a category method (verified in iOS 7 and doesn't use private API):
#implementation UISearchBar (MyAdditions)
- (void)changeDefaultBackgroundColor:(UIColor *)color {
for (UIView *subview in self.subviews) {
for (UIView *subSubview in subview.subviews) {
if ([subSubview isKindOfClass:[UITextField class]]) {
UITextField *searchField = (UITextField *)subSubview;
searchField.backgroundColor = color;
break;
}
}
}
}
#end
So after importing the category into your class, just use it like:
[self.searchBar changeDefaultBackgroundColor:[UIColor grayColor]];
Keep in mind, if you put this immediately after the [[UISearchBar alloc] init] line, it won't work yet since the subviews of the search bar are still being created. Put it a few lines down after you setup the rest of the search bar.
- (void)viewDidLoad
{
[super viewDidLoad];
[[self searchSubviewsForTextFieldIn:self.searchBar] setBackgroundColor:[UIColor redColor]];
}
- (UITextField*)searchSubviewsForTextFieldIn:(UIView*)view
{
if ([view isKindOfClass:[UITextField class]]) {
return (UITextField*)view;
}
UITextField *searchedTextField;
for (UIView *subview in view.subviews) {
searchedTextField = [self searchSubviewsForTextFieldIn:subview];
if (searchedTextField) {
break;
}
}
return searchedTextField;
}
This is the Swift version ( swift 2.1 /IOS 9 )
for view in searchBar.subviews {
for subview in view.subviews {
if subview .isKindOfClass(UITextField) {
let textField: UITextField = subview as! UITextField
textField.backgroundColor = UIColor.lightGrayColor()
}
}
}
iOS 13, Swift 5
searchBar.searchTextField.backgroundColor = .gray
searchBar.searchTextField.tintColor = .white
searchBar.searchTextField.textColor = .white
Searchbar now has a new instance property SearchTextField starting iOS 13,
https://developer.apple.com/documentation/uikit/uisearchbar/3175433-searchtextfield
if(#available(iOS 13, *))
searchBar.searchTextField.backgroundColor = [UIColor whiteColor];
searchBar.searchTextField.textColor = [UIColor blackColor];
else{
//API that supports below iOS 13
//This will set it for all the UISearchBars in your application
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setBackgroundColor:[UIColor whiteColor]];
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTextColor:[UIColor blackColor]];
}
For iOS 9 use this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Remove lag on oppening the keyboard for the first time
UITextField *lagFreeField = [[UITextField alloc] init];
[self.window addSubview:lagFreeField];
[lagFreeField becomeFirstResponder];
[lagFreeField resignFirstResponder];
[lagFreeField removeFromSuperview];
//searchBar background color change
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setBackgroundColor:[UIColor greenColor]];
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTextColor:[UIColor blackColor];
return YES;
}
Swift 3
for subview in searchBar.subviews {
for innerSubview in subview.subviews {
if innerSubview is UITextField {
innerSubview.backgroundColor = UIColor.YOUR_COLOR_HERE
}
}
}
For Swift 3+, use this:
for subView in searchController.searchBar.subviews {
for subViewOne in subView.subviews {
if let textField = subViewOne as? UITextField {
subViewOne.backgroundColor = UIColor.red
//use the code below if you want to change the color of placeholder
let textFieldInsideUISearchBarLabel = textField.value(forKey: "placeholderLabel") as? UILabel
textFieldInsideUISearchBarLabel?.textColor = UIColor.blue
}
}
}
With Swift 4, I would recommend that you only do this, no additional code needed:
self.searchBar.searchBarStyle = .prominent
self.searchBar.barStyle = .black
You can also change .prominent to .minimal should you not want the outer background to be grey.
#EvGeniy Ilyin EvGeniy Ilyin's solution is the best.
I wrote an Objective-C version based on this solution.
Create a UIImage category, and advertise two class methods in UIImage+YourCategory.h
+ (UIImage *)imageWithColor:(UIColor *)color withSize:(CGRect)imageRect;
+ (UIImage *)roundImage:(UIImage *)image withRadius:(CGFloat)radius;
Implement methods in UIImage+YourCategory.m
// create image with your color
+ (UIImage *)imageWithColor:(UIColor *)color withSize:(CGRect)imageRect
{
UIGraphicsBeginImageContext(imageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, imageRect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
// get a rounded-corner image from UIImage instance with your radius
+ (UIImage *)roundImage:(UIImage *)image withRadius:(CGFloat)radius
{
CGRect rect = CGRectMake(0.0, 0.0, 0.0, 0.0);
rect.size = image.size;
UIGraphicsBeginImageContextWithOptions(image.size, NO, [UIScreen mainScreen].scale);
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect
cornerRadius:radius];
[path addClip];
[image drawInRect:rect];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Make your own UISearchBar in your ViewController
CGRect rect = CGRectMake(0.0, 0.0, 44.0, 30.0);
UIImage *colorImage = [UIImage imageWithColor:[UIColor yourColor] withSize:rect];
UIImage *finalImage = [UIImage roundImage:colorImage withRadius:4.0];
[yourSearchBar setSearchFieldBackgroundImage:finalImage forState:UIControlStateNormal];
This Worked for me.
- (void)setupSearchBar
{
[self.searchBar setReturnKeyType:UIReturnKeySearch];
[self.searchBar setEnablesReturnKeyAutomatically:NO];
[self.searchBar setPlaceholder:FOLocalizedString(#"search", nil)];
[self.searchBar setBackgroundImage:[UIImage new]];
[self.searchBar setBackgroundColor:[UIColor myGreyBGColor]];
[self.searchBar setBarTintColor:[UIColor myGreyBGColor]];
[self.searchBar setTintColor:[UIColor blueColor]];
}
Use it to update search textfield backgroud_color for xcode10 and xcode11 working fine for me
[[UITextField appearanceWhenContainedInInstancesOfClasses:#[[UISearchBar class]]] setTextColor:[UIColor greenColor]];
This helped me to change the background color of textField in searchbar.
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).backgroundColor = .white

Fade edges of UITableView

I've made a little research about my problem and unfortunately there was no solution for my problem.
The closest was Fade UIImageView as it approaches the edges of a UIScrollView but it's still not for me.
I want my table to apply an "invisibility gradient" on the top. If the cell is at a 50px distance from the top edge it starts to vanish. The closer it is to the upper edge, the more invisible the part is. The cells height is about 200 pixels, so the lower part of the cell need to be visible in 100%. But nevermind - I need a table view (or table view container) to do this task, because similar tables can display other cells.
If the table is a subview of a solid color view, I can achieve that by adding an image which is a horizontal gradient that I can streach to any width. The top pixel of that image starts with the exact color of the background, and going down the same color has less alpha.
But...
we have a UITableView with transparent color. Below the table there is no solid color, but a pattern image/texture, that can also be different on other screens of the app.
Do you have any Idea how I can achieve this behaviour?
Regards
I took this tutorial and made some changes and additions:
It now works on all tableviews - even if they are part of bigger screen.
It works regardless of the background or whatever is behind the tableview.
The mask changes depends on the position of the table view - when scrolled to top only the bottom faded, in when scrolled to bottom only top is faded...
1. Start by importing QuartzCore and setting a mask layer in your controller:
EDIT: No need for reference to CAGradientLayer in class.
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#interface mViewController : UIViewController
.
.
#end
2. Add this to viewWillAppear viewDidLayoutSubviews:
(See #Darren's comment on this one)
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
if (!self.tableView.layer.mask)
{
CAGradientLayer *maskLayer = [CAGradientLayer layer];
maskLayer.locations = #[[NSNumber numberWithFloat:0.0],
[NSNumber numberWithFloat:0.2],
[NSNumber numberWithFloat:0.8],
[NSNumber numberWithFloat:1.0]];
maskLayer.bounds = CGRectMake(0, 0,
self.tableView.frame.size.width,
self.tableView.frame.size.height);
maskLayer.anchorPoint = CGPointZero;
self.tableView.layer.mask = maskLayer;
}
[self scrollViewDidScroll:self.tableView];
}
3. Make sure you are a delegate of UIScrollViewDelegate by adding it in the .h of your controller:
#interface mViewController : UIViewController <UIScrollViewDelegate>
4. To finish, implement scrollViewDidScroll in your controller .m:
#pragma mark - Scroll View Delegate Methods
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGColorRef outerColor = [UIColor colorWithWhite:1.0 alpha:0.0].CGColor;
CGColorRef innerColor = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor;
NSArray *colors;
if (scrollView.contentOffset.y + scrollView.contentInset.top <= 0) {
//Top of scrollView
colors = #[(__bridge id)innerColor, (__bridge id)innerColor,
(__bridge id)innerColor, (__bridge id)outerColor];
} else if (scrollView.contentOffset.y + scrollView.frame.size.height
>= scrollView.contentSize.height) {
//Bottom of tableView
colors = #[(__bridge id)outerColor, (__bridge id)innerColor,
(__bridge id)innerColor, (__bridge id)innerColor];
} else {
//Middle
colors = #[(__bridge id)outerColor, (__bridge id)innerColor,
(__bridge id)innerColor, (__bridge id)outerColor];
}
((CAGradientLayer *)scrollView.layer.mask).colors = colors;
[CATransaction begin];
[CATransaction setDisableActions:YES];
scrollView.layer.mask.position = CGPointMake(0, scrollView.contentOffset.y);
[CATransaction commit];
}
Again: most of the solution is from this tutorial in cocoanetics.
This is a translation of Aviel Gross's answer to Swift
import UIKit
class mViewController: UIViewController, UIScrollViewDelegate {
//Emitted boilerplate code
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if self.tableView.layer.mask == nil {
//If you are using auto layout
//self.view.layoutIfNeeded()
let maskLayer: CAGradientLayer = CAGradientLayer()
maskLayer.locations = [0.0, 0.2, 0.8, 1.0]
let width = self.tableView.frame.size.width
let height = self.tableView.frame.size.height
maskLayer.bounds = CGRect(x: 0.0, y: 0.0, width: width, height: height)
maskLayer.anchorPoint = CGPoint.zero
self.tableView.layer.mask = maskLayer
}
scrollViewDidScroll(self.tableView)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let outerColor = UIColor(white: 1.0, alpha: 0.0).cgColor
let innerColor = UIColor(white: 1.0, alpha: 1.0).cgColor
var colors = [CGColor]()
if scrollView.contentOffset.y + scrollView.contentInset.top <= 0 {
colors = [innerColor, innerColor, innerColor, outerColor]
} else if scrollView.contentOffset.y + scrollView.frame.size.height >= scrollView.contentSize.height {
colors = [outerColor, innerColor, innerColor, innerColor]
} else {
colors = [outerColor, innerColor, innerColor, outerColor]
}
if let mask = scrollView.layer.mask as? CAGradientLayer {
mask.colors = colors
CATransaction.begin()
CATransaction.setDisableActions(true)
mask.position = CGPoint(x: 0.0, y: scrollView.contentOffset.y)
CATransaction.commit()
}
}
//Emitted boilerplate code
}
Swift version of #victorfigol's excellent solution:
class FadingTableView : UITableView {
var percent = Float(0.05)
private let outerColor = UIColor(white: 1.0, alpha: 0.0).cgColor
private let innerColor = UIColor(white: 1.0, alpha: 1.0).cgColor
override func awakeFromNib() {
super.awakeFromNib()
addObserver(self, forKeyPath: "bounds", options: NSKeyValueObservingOptions(rawValue: 0), context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if object is FadingTableView && keyPath == "bounds" {
initMask()
}
}
deinit {
removeObserver(self, forKeyPath:"bounds")
}
override func layoutSubviews() {
super.layoutSubviews()
updateMask()
}
func initMask() {
let maskLayer = CAGradientLayer()
maskLayer.locations = [0.0, NSNumber(value: percent), NSNumber(value:1 - percent), 1.0]
maskLayer.bounds = CGRect(x:0, y:0, width:frame.size.width, height:frame.size.height)
maskLayer.anchorPoint = CGPoint.zero
self.layer.mask = maskLayer
updateMask()
}
func updateMask() {
let scrollView : UIScrollView = self
var colors = [CGColor]()
if scrollView.contentOffset.y <= -scrollView.contentInset.top { // top
colors = [innerColor, innerColor, innerColor, outerColor]
}
else if (scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height { // bottom
colors = [outerColor, innerColor, innerColor, innerColor]
}
else {
colors = [outerColor, innerColor, innerColor, outerColor]
}
if let mask = scrollView.layer.mask as? CAGradientLayer {
mask.colors = colors
CATransaction.begin()
CATransaction.setDisableActions(true)
mask.position = CGPoint(x: 0.0, y: scrollView.contentOffset.y)
CATransaction.commit()
}
}
}
This is my version of the fading table view by inheriting UITableView. Tested for iOS 7 & 8.
There is no need to implement scrollViewDidScroll, layoutSubviews can be used instead.
By observing for bounds changes, the mask is
always properly placed when the rotation changes.
You can use the percent parameter to change how much fading you want at the edges, I
find a small value looking better in some cases.
CEFadingTableView.m
#import "CEFadingTableView.h"
#interface CEFadingTableView()
#property (nonatomic) float percent; // 1 - 100%
#end
#implementation CEFadingTableView
- (void)awakeFromNib
{
[super awakeFromNib];
self.percent = 5.0f;
[self addObserver:self forKeyPath:#"bounds" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if(object == self && [keyPath isEqualToString:#"bounds"])
{
[self initMask];
}
}
- (void)dealloc
{
[self removeObserver:self forKeyPath:#"bounds"];
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self updateMask];
}
- (void)initMask
{
CAGradientLayer *maskLayer = [CAGradientLayer layer];
maskLayer.locations = #[#(0.0f), #(_percent / 100), #(1 - _percent / 100), #(1.0f)];
maskLayer.bounds = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
maskLayer.anchorPoint = CGPointZero;
self.layer.mask = maskLayer;
[self updateMask];
}
- (void)updateMask
{
UIScrollView *scrollView = self;
CGColorRef outer = [UIColor colorWithWhite:1.0 alpha:0.0].CGColor;
CGColorRef inner = [UIColor colorWithWhite:1.0 alpha:1.0].CGColor;
NSArray *colors = #[(__bridge id)outer, (__bridge id)inner, (__bridge id)inner, (__bridge id)outer];
if(scrollView.contentOffset.y <= 0) // top
{
colors = #[(__bridge id)inner, (__bridge id)inner, (__bridge id)inner, (__bridge id)outer];
}
else if((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height) // bottom
{
colors = #[(__bridge id)outer, (__bridge id)inner, (__bridge id)inner, (__bridge id)inner];
}
((CAGradientLayer *)scrollView.layer.mask).colors = colors;
[CATransaction begin];
[CATransaction setDisableActions:YES];
scrollView.layer.mask.position = CGPointMake(0, scrollView.contentOffset.y);
[CATransaction commit];
}
#end

Resources