import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "http://blogs.independent.co.uk/wp-content/uploads/2012/12/some-girls.jpg")
var image : UIImage? = nil
if let data = NSData(contentsOfURL: url!) {
image = UIImage(data: data)
}
let myLayer = CALayer()
myLayer.anchorPoint = CGPoint(x: 0, y: 0)
myLayer.bounds = CGRectMake(0, 0, self.view.bounds.width, self.view.bounds.height)
myLayer.position = CGPoint(x: 0,y: 0)
myLayer.borderColor = UIColor.redColor().CGColor
myLayer.borderWidth = 2.0
// the image is not displayed
myLayer.contents = image
print(myLayer.contents)
myView.layer.addSublayer(myLayer)
myView.layer.opacity = 0.3
}
}
The UIImage is not being displayed why is that? whats wrong with the code? Please Help anyone who is well versed in Core Animation and Swift
In CALayer CGImage is used and not UIImage Hence Convert the UIImage into CGImage like this :
myLayer.contents = image?.CGImage
This should fix that.
Related
I need to draw in CALayer subclass NSAttributedString with background for every line of text and insets(horizontal and vertical). How to do that?
I am trying this code. But, I can't set insets text insets.
override func draw(in ctx: CGContext) {
if let attributedString = nsAttributedString {
ctx.translateBy(x: 0, y: (bounds.size.height * 1.5) - (stringSize.height / 2))
ctx.scaleBy(x: 1.0, y: -1.0)
let path = CGMutablePath()
path.addRect(bounds)
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
let frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, attributedString.length),
path, nil)
ctx.textMatrix = .identity
ctx.textPosition = .zero
CTFrameDraw(frame, ctx)
}
}
Its should looks like this
- (void) drawTextLink:(NSString *) text inFrame:(CGRect) frameRect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGAffineTransform ctm = CGContextGetCTM(context);
// Translate the origin to the bottom left.
// Notice that 842 is the size of the PDF page.
CGAffineTransformTranslate(ctm, 0.0, 842);
// Flip the handedness of the coordinate system back to right handed.
CGAffineTransformScale(ctm, 1.0, -1.0);
// Convert the update rectangle to the new coordiante system.
CGRect xformRect = CGRectApplyAffineTransform(frameRect, ctm);
NSURL *url = [NSURL URLWithString:text];
UIGraphicsSetPDFContextURLForRect( url, xformRect );
CGContextSaveGState(context);
NSDictionary *attributesDict;
NSMutableAttributedString *attString;
NSNumber *underline = [NSNumber numberWithInt:NSUnderlineStyleSingle];
attributesDict = #{NSUnderlineStyleAttributeName : underline,
NSForegroundColorAttributeName : [UIColor blueColor]};
attString = [[NSMutableAttributedString alloc] initWithString:url.absoluteString attributes:attributesDict];
[attString drawInRect:frameRect];
CGContextRestoreGState(context);
}
It's code I found to make url's in a pdf clickable.
I'm still getting the hang of objective-c so your help is greatly apprecaited. Thank you very much in advance!
Right now here is what I have translated, and I'm pretty sure there are a couple mistakes:
func drawTextLink(text: NSString, frameRect: CGRect) {
let context: CGContext = UIGraphicsGetCurrentContext()!
var c = context.ctm
let cc = CGAffineTransform.translatedBy(c)
cc.(0.0, 842)
}
Above method in Swift 4.0 should be like this.
func drawTextLink(text: String, frame: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else {return}
let ctm = context.ctm
ctm.translatedBy(x: 0.0, y: 842)
ctm.scaledBy(x: 1.0, y: -1.0)
let newFrame = frame.applying(ctm)
guard let url = URL(string: text) else {return}
UIGraphicsSetPDFContextURLForRect(url, newFrame)
context.saveGState()
let dict = [NSAttributedStringKey.underlineStyle: 1, .foregroundColor: UIColor.red] as [NSAttributedStringKey : Any]
let attrString = NSAttributedString(string: text, attributes: dict)
attrString.draw(in: frame)
context.restoreGState()
}
I am trying to take a screenshot of my UIView and Crop it, save it to my Photo library. As i am trying to do this there are 3 conflicts.
(1) - I want to take Screenshot with Blur in it, As blur filter never gets saved in the screenshot.
(2) - The image quality is very low.
(3) - I am not able to crop the image.
This is my code -
#IBAction func Screenshot(_ sender: UIButton) {
// Declare the snapshot boundaries
let top: CGFloat = 70
let bottom: CGFloat = 400
// The size of the cropped image
let size = CGSize(width: view.frame.size.width, height: view.frame.size.height - top - bottom)
// Start the context
UIGraphicsBeginImageContext(size)
// we are going to use context in a couple of places
let context = UIGraphicsGetCurrentContext()!
// Transform the context so that anything drawn into it is displaced "top" pixels up
// Something drawn at coordinate (0, 0) will now be drawn at (0, -top)
// This will result in the "top" pixels being cut off
// The bottom pixels are cut off because the size of the of the context
context.translateBy(x: 0, y: -top)
// Draw the view into the context (this is the snapshot)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let snapshot = UIGraphicsGetImageFromCurrentImageContext()
// End the context (this is required to not leak resources)
UIGraphicsEndImageContext()
// Save to photos
UIImageWriteToSavedPhotosAlbum(snapshot!, nil, nil, nil)
}
You said:
I want to take Screenshot with Blur in it, As blur filter never gets saved in the screenshot.
I wonder if the view being snapshotted might not be the one with the UIVisualEffectView as a subview. Because when I use the code at the end of the answer, the blur effect (and the impact of changing the fractionCompleted) is captured.
The image quality is very low.
If you use UIGraphicsBeginImageContextWithOptions with a scale of zero, it should capture the image at the resolution of the device:
UIGraphicsBeginImageContextWithOptions(size, isOpaque, 0)
I am not able to crop the image.
I personally capture the whole view, and then crop as needed. See UIView extension below.
In Swift 3:
class ViewController: UIViewController {
var animator: UIViewPropertyAnimator?
#IBOutlet weak var imageView: UIImageView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let blur = UIBlurEffect(style: .light)
let effectView = UIVisualEffectView(effect: blur)
view.addSubview(effectView)
effectView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
effectView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor),
effectView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor),
effectView.topAnchor.constraint(equalTo: imageView.topAnchor),
effectView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor)
])
animator = UIViewPropertyAnimator(duration: 0, curve: .linear) { effectView.effect = nil }
}
#IBAction func didChangeValueForSlider(_ sender: UISlider) {
animator?.fractionComplete = CGFloat(sender.value)
}
#IBAction func didTapSnapshotButton(_ sender: AnyObject) {
if let snapshot = view.snapshot(of: imageView.frame) {
UIImageWriteToSavedPhotosAlbum(snapshot, nil, nil, nil)
}
}
}
extension UIView {
/// Create snapshot
///
/// - parameter rect: The `CGRect` of the portion of the view to return. If `nil` (or omitted),
/// return snapshot of the whole view.
///
/// - returns: Returns `UIImage` of the specified portion of the view.
func snapshot(of rect: CGRect? = nil) -> UIImage? {
// snapshot entire view
UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
drawHierarchy(in: bounds, afterScreenUpdates: true)
let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// if no `rect` provided, return image of whole view
guard let image = wholeImage, let rect = rect else { return wholeImage }
// otherwise, grab specified `rect` of image
let scale = image.scale
let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
guard let cgImage = image.cgImage?.cropping(to: scaledRect) else { return nil }
return UIImage(cgImage: cgImage, scale: scale, orientation: .up)
}
}
}
Or in Swift 2:
class ViewController: UIViewController {
var animator: UIViewPropertyAnimator?
#IBOutlet weak var imageView: UIImageView!
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let blur = UIBlurEffect(style: .Light)
let effectView = UIVisualEffectView(effect: blur)
view.addSubview(effectView)
effectView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
effectView.leadingAnchor.constraintEqualToAnchor(imageView.leadingAnchor),
effectView.trailingAnchor.constraintEqualToAnchor(imageView.trailingAnchor),
effectView.topAnchor.constraintEqualToAnchor(imageView.topAnchor),
effectView.bottomAnchor.constraintEqualToAnchor(imageView.bottomAnchor)
])
animator = UIViewPropertyAnimator(duration: 0, curve: .Linear) { effectView.effect = nil }
}
#IBAction func didChangeValueForSlider(sender: UISlider) {
animator?.fractionComplete = CGFloat(sender.value)
}
#IBAction func didTapSnapshotButton(sender: AnyObject) {
if let snapshot = view.snapshot(of: imageView.frame) {
UIImageWriteToSavedPhotosAlbum(snapshot, nil, nil, nil)
}
}
}
extension UIView {
/// Create snapshot
///
/// - parameter rect: The `CGRect` of the portion of the view to return. If `nil` (or omitted),
/// return snapshot of the whole view.
///
/// - returns: Returns `UIImage` of the specified portion of the view.
func snapshot(of rect: CGRect? = nil) -> UIImage? {
// snapshot entire view
UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, 0)
drawViewHierarchyInRect(bounds, afterScreenUpdates: true)
let wholeImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
// if no `rect` provided, return image of whole view
guard let rect = rect, let image = wholeImage else { return wholeImage }
// otherwise, grab specified `rect` of image
let scale = image.scale
let scaledRect = CGRect(x: rect.origin.x * scale, y: rect.origin.y * scale, width: rect.size.width * scale, height: rect.size.height * scale)
guard let cgImage = CGImageCreateWithImageInRect(image.CGImage!, scaledRect) else { return nil }
return UIImage(CGImage: cgImage, scale: scale, orientation: .Up)
}
}
So, when I capture four images at four different slider positions, that yields:
I am not able to crop the image in the right way, As there is navigation bar and status bar showing with blank (White) background. (Rest of the image crops well).
here is the code -
let top: CGFloat = 70
let bottom: CGFloat = 280
// The size of the cropped image
let size = CGSize(width: view.frame.size.width, height: view.frame.size.height - top - bottom)
// Start the context
UIGraphicsBeginImageContext(size)
// we are going to use context in a couple of places
let context = UIGraphicsGetCurrentContext()!
// Transform the context so that anything drawn into it is displaced "top" pixels up
// Something drawn at coordinate (0, 0) will now be drawn at (0, -top)
// This will result in the "top" pixels being cut off
// The bottom pixels are cut off because the size of the of the context
context.translateBy(x: 0, y: 0)
// Draw the view into the context (this is the snapshot)
UIGraphicsBeginImageContextWithOptions(size,view.isOpaque, 0)
self.view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let snapshot = UIGraphicsGetImageFromCurrentImageContext()
I'm working on a design application that has a section for selecting colors by three sliders for RGB.
As we can see in xcode, where we want to select a color by RGB values, the slider tint color is a gradient color that changes when we change the sliders. I want to use this in my application. but I have no idea about how to do this?
I've found this code in a blog. but didn't work for me.
- (void)setGradientToSlider:(UISlider *)Slider WithColors:(NSArray *)Colors{
UIView * view = (UIView *)[[Slider subviews]objectAtIndex:0];
UIImageView * maxTrackImageView = (UIImageView *)[[view subviews]objectAtIndex:0];
CAGradientLayer * maxTrackGradient = [CAGradientLayer layer];
CGRect rect = maxTrackImageView.frame;
rect.origin.x = view.frame.origin.x;
maxTrackGradient.frame = rect;
maxTrackGradient.colors = Colors;
[maxTrackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[maxTrackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[[maxTrackImageView layer] insertSublayer:maxTrackGradient atIndex:0];
/////////////////////////////////////////////////////
UIImageView * minTrackImageView = (UIImageView *)[[view subviews]objectAtIndex:1];
CAGradientLayer * minTrackGradient = [CAGradientLayer layer];
rect = minTrackImageView.frame;
rect.size.width = maxTrackImageView.frame.size.width;
rect.origin.x = 0;
rect.origin.y = 0;
minTrackGradient.frame = rect;
minTrackGradient.colors = Colors;
[minTrackGradient setStartPoint:CGPointMake(0.0, 0.5)];
[minTrackGradient setEndPoint:CGPointMake(1.0, 0.5)];
[minTrackImageView.layer insertSublayer:minTrackGradient atIndex:0];
}
I would appreciate any helps. Thanks.
While it didnt give me the desired results here is a down and dirty Swift version of the answer above for those that want to try it.
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRectMake(0, 0, slider.frame.size.width, 5)
tgl.frame = frame
tgl.colors = [UIColor.blueColor().CGColor, UIColor.greenColor().CGColor, UIColor.yellowColor().CGColor, UIColor.orangeColor().CGColor, UIColor.redColor().CGColor]
tgl.startPoint = CGPointMake(0.0, 0.5)
tgl.endPoint = CGPointMake(1.0, 0.5)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, tgl.opaque, 0.0);
tgl.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
image.resizableImageWithCapInsets(UIEdgeInsetsZero)
slider.setMinimumTrackImage(image, forState: .Normal)
//slider.setMaximumTrackImage(image, forState: .Normal)
}
UPDATE for Swift 4.0
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRect.init(x:0, y:0, width:slider.frame.size.width, height:5)
tgl.frame = frame
tgl.colors = [UIColor.blue.cgColor, UIColor.green.cgColor, UIColor.yellow.cgColor, UIColor.orange.cgColor, UIColor.red.cgColor]
tgl.startPoint = CGPoint.init(x:0.0, y:0.5)
tgl.endPoint = CGPoint.init(x:1.0, y:0.5)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, tgl.isOpaque, 0.0);
tgl.render(in: UIGraphicsGetCurrentContext()!)
if let image = UIGraphicsGetImageFromCurrentImageContext() {
UIGraphicsEndImageContext()
image.resizableImage(withCapInsets: UIEdgeInsets.zero)
slider.setMinimumTrackImage(image, for: .normal)
}
}
Here is possible solution:
Usage:
//array of CGColor objects, color1 and color2 are UIColor objects
NSArray *colors = [NSArray arrayWithObjects:(id)color1.CGColor, (id)color2.CGColor, nil];
//your UISlider
[slider setGradientBackgroundWithColors:colors];
Implementation:
Create category on UISlider:
- (void)setGradientBackgroundWithColors:(NSArray *)colors
{
CAGradientLayer *trackGradientLayer = [CAGradientLayer layer];
CGRect frame = self.frame;
frame.size.height = 5.0; //set the height of slider
trackGradientLayer.frame = frame;
trackGradientLayer.colors = colors;
//setting gradient as horizontal
trackGradientLayer.startPoint = CGPointMake(0.0, 0.5);
trackGradientLayer.endPoint = CGPointMake(1.0, 0.5);
UIImage *trackImage = [[UIImage imageFromLayer:trackGradientLayer] resizableImageWithCapInsets:UIEdgeInsetsZero];
[self setMinimumTrackImage:trackImage forState:UIControlStateNormal];
[self setMaximumTrackImage:trackImage forState:UIControlStateNormal];
}
Where colors is array of CGColor.
I have also created a category on UIImage which creates image from layer as you need an UIImage for setting gradient on slider.
+ (UIImage *)imageFromLayer:(CALayer *)layer
{
UIGraphicsBeginImageContextWithOptions(layer.frame.size, layer.opaque, 0.0);
[layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *outputImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return outputImage;
}
For Swift 3 and to prevent the slider from scaling the Min image, apply this when setting the its image. Recalculating the slider's left side is not necessary. Only recalc if you can changing the color of the gradient. The Max image does not seem to scale, but you should probably apply the same setting for consistency. There is a slight difference on the Max image when not applying its insets.
slider.setMinimumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
For some reason it only works properly when resizableImage(withCapInsets:.zero) is all done at the same time. Running that part separate does not allow the image to work and gets scaled.
Here is the entire routine in Swift 3:
func setSlider(slider:UISlider) {
let tgl = CAGradientLayer()
let frame = CGRect(x: 0.0, y: 0.0, width: slider.bounds.width, height: 5.0 )
tgl.frame = frame
tgl.colors = [ UIColor.yellow.cgColor,UIColor.black.cgColor]
tgl.endPoint = CGPoint(x: 1.0, y: 1.0)
tgl.startPoint = CGPoint(x: 0.0, y: 1.0)
UIGraphicsBeginImageContextWithOptions(tgl.frame.size, false, 0.0)
tgl.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
slider.setMaximumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
slider.setMinimumTrackImage(image?.resizableImage(withCapInsets:.zero), for: .normal)
}
This is a really effective approach that I've found after a lot of web search. So it's better to share it here as a complete answer. The following code is a Swift Class That you can use to create and use gradients as UIView or UIImage.
import Foundation
import UIKit
class Gradient: UIView{
// Gradient Color Array
private var Colors: [UIColor] = []
// Start And End Points Of Linear Gradient
private var SP: CGPoint = CGPoint.zeroPoint
private var EP: CGPoint = CGPoint.zeroPoint
// Start And End Center Of Radial Gradient
private var SC: CGPoint = CGPoint.zeroPoint
private var EC: CGPoint = CGPoint.zeroPoint
// Start And End Radius Of Radial Gradient
private var SR: CGFloat = 0.0
private var ER: CGFloat = 0.0
// Flag To Specify If The Gradient Is Radial Or Linear
private var flag: Bool = false
// Some Overrided Init Methods
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
// Draw Rect Method To Draw The Graphics On The Context
override func drawRect(rect: CGRect) {
// Get Context
let context = UIGraphicsGetCurrentContext()
// Get Color Space
let colorSpace = CGColorSpaceCreateDeviceRGB()
// Create Arrays To Convert The UIColor to CG Color
var colorComponent: [CGColor] = []
var colorLocations: [CGFloat] = []
var i: CGFloat = 0.0
// Add Colors Into The Color Components And Use An Index Variable For Their Location In The Array [The Location Is From 0.0 To 1.0]
for color in Colors {
colorComponent.append(color.CGColor)
colorLocations.append(i)
i += CGFloat(1.0) / CGFloat(self.Colors.count - 1)
}
// Create The Gradient With The Colors And Locations
let gradient: CGGradientRef = CGGradientCreateWithColors(colorSpace, colorComponent, colorLocations)
// Create The Suitable Gradient Based On Desired Type
if flag {
CGContextDrawRadialGradient(context, gradient, SC, SR, EC, ER, 0)
} else {
CGContextDrawLinearGradient(context, gradient, SP, EP, 0)
}
}
// Get The Input Data For Linear Gradient
func CreateLinearGradient(startPoint: CGPoint, endPoint: CGPoint, colors: UIColor...) {
self.Colors = colors
self.SP = startPoint
self.EP = endPoint
self.flag = false
}
// Get The Input Data For Radial Gradient
func CreateRadialGradient(startCenter: CGPoint, startRadius: CGFloat, endCenter: CGPoint, endRadius: CGFloat, colors: UIColor...) {
self.Colors = colors
self.SC = startCenter
self.EC = endCenter
self.SR = startRadius
self.ER = endRadius
self.flag = true
}
// Function To Convert Gradient To UIImage And Return It
func getImage() -> UIImage {
// Begin Image Context
UIGraphicsBeginImageContext(self.bounds.size)
// Draw The Gradient
self.drawRect(self.frame)
// Get Image From The Current Context
let image = UIGraphicsGetImageFromCurrentImageContext()
// End Image Context
UIGraphicsEndImageContext()
// Return The Result Gradient As UIImage
return image
}
}
I'm trying to make parallax photo effect in my app using CSStickyHeaderFlowLayout
- https://github.com/jamztang/CSStickyHeaderFlowLayout/
and Swift language.
I have installed necessary Pod's file, add #import "CSStickyHeaderFlowLayout.h" to my "appName"-Bridging-Header.h file, and implemented this method:
func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
var cell: UICollectionViewCell = UICollectionViewCell(frame: CGRectMake(0, 0, 400, 200));
var imageView: UIImageView = UIImageView();
var image: UIImage = UIImage(named: "news2.jpg")!;
imageView.image = image;
cell.addSubview(imageView);
return cell;
}
but now I have difficulties to translate this Obj-C code to Swift:
CSStickyHeaderFlowLayout *layout = (id)self.collectionViewLayout;
if ([layout isKindOfClass:[CSStickyHeaderFlowLayout class]]) {
layout.parallaxHeaderReferenceSize = CGSizeMake(320, 200);
}
In my opinion after that step my effect should works. Am I right? Or I have missed something?
Thanks for help,
m.af
You should be able to do it like this in Swift
if let layout = self.collectionViewLayout as? CSStickyHeaderFlowLayout {
layout.parallaxHeaderReferenceSize = CGSizeMake(320, 200)
}
You could try this:
var layout = CSStickyHeaderFlowLayout()
if (layout.isKindOfClass(CSStickyHeaderFlowLayout.self)) {
layout.parallaxHeaderReferenceSize = CGSizeMake(self.view.frame.size.width, 426)
layout.parallaxHeaderMinimumReferenceSize = CGSizeMake(self.view.frame.size.width, 110)
layout.itemSize = CGSizeMake(self.view.frame.size.width, layout.itemSize.height)
layout.parallaxHeaderAlwaysOnTop = true
// If we want to disable the sticky header effect
layout.disableStickyHeaders = true
}