Swift Image masking - ios

Trying to achieve something like below picture. Where the image has a mask to display certain portion of the image. Here is the code to create the shape
let shape = CAShapeLayer()
shape.opacity = 0.5
shape.lineWidth = 2
shape.lineJoin = kCALineJoinMiter
let path = UIBezierPath()
path.moveToPoint(CGPointMake(0 , 0))
path.addLineToPoint(CGPointMake(200, 0))
path.addLineToPoint(CGPointMake(160, 200))
path.addLineToPoint(CGPointMake(0, 200))
path.closePath()
shape.path = path.CGPath
Is there a way to add image into this layer. so its bound are set respective to the shape? BEFORE/AFTER image can be ignored.
Any leads would be appreciated. Thanks!

This is the solution I came up with. You need to have proper mask image.
import UIKit
import SnapKit
class TestScreenController: UIViewController {
let beforeView = UIImageView()
let afterView = UIImageView()
let maskView = UIImageView()
let divider = UIImageView(image: UIImage(named: "before_after_image"))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
setup()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setup(){
self.view.addSubview(maskView)
self.maskView.addSubview(beforeView)
self.maskView.addSubview(afterView)
self.maskView.addSubview(divider)
maskView.snp_makeConstraints { (make) in
make.top.equalTo(self.view).offset(50)
make.width.equalTo(300)
make.height.equalTo(110)
make.centerX.equalTo(self.view)
}
maskView.layer.cornerRadius = 15
maskView.layer.masksToBounds = true
let image = UIImage(named: "beforeWash-min.png")
let maskingImage = UIImage(named: "beforeImageM-1")
beforeView.image = maskImage(image!, mask: maskingImage!)
beforeView.contentMode = .ScaleToFill
beforeView.snp_makeConstraints { (make) in
make.top.left.height.equalTo(self.maskView)
make.width.equalTo(self.maskView).multipliedBy(0.50).offset(12)
}
let imageA = UIImage(named: "afterWash-min.png")
let maskingImageA = UIImage(named: "afterImageM-1")
afterView.image = maskImage(imageA!, mask: maskingImageA!)
afterView.contentMode = .ScaleToFill
afterView.snp_makeConstraints { (make) in
make.top.right.height.equalTo(self.maskView)
make.width.equalTo(self.maskView).multipliedBy(0.50).offset(10)
}
divider.snp_makeConstraints { (make) in
make.center.equalTo(maskView)
make.height.equalTo(maskView)
}
}
func maskImage(image:UIImage, mask:(UIImage))->UIImage{
let imageReference = image.CGImage
let maskReference = mask.CGImage
let imageMask = CGImageMaskCreate(CGImageGetWidth(maskReference),
CGImageGetHeight(maskReference),
CGImageGetBitsPerComponent(maskReference),
CGImageGetBitsPerPixel(maskReference),
CGImageGetBytesPerRow(maskReference),
CGImageGetDataProvider(maskReference), nil, true)
let maskedReference = CGImageCreateWithMask(imageReference, imageMask)
let maskedImage = UIImage(CGImage:maskedReference!)
return maskedImage
}
}

Related

How do you display an image at the maximum available space?

I check if the image is vertical or horizontal. If it is horizontal, I rotate it:
#IBOutlet private weak var img: UIImageView!
img.image = file.image
let imageSize = file.image?.size
let imgWidth = imageSize?.width ?? 0
let imgHeight = imageSize?.height ?? 0
if imgWidth > imgHeight {
print("IMG HORIZONTAL")
imgDetail.transform = imgDetail.transform.rotated(by: .pi / 2)
} else {
print("IMG VERTICAL")
}
But it leaves me a space around the image. I would like it to be at the maximum size of the UIImageView.
Try this, declare your imageView:
let YourImageView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "myImage")?.withRenderingMode(.alwaysOriginal)
iv.backgroundColor = .clear
iv.contentMode = .scaleAspectFit
iv.isUserInteractionEnabled = true
iv.clipsToBounds = true
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
in viewDidLoad set if statement and call setupConstraints:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
guard let imgWidth = YourImageView.image?.size.width else { return }
guard let imgHeight = YourImageView.image?.size.height else { return }
if imgWidth > imgHeight {
print("IMG HORIZONTAL")
guard let image = YourImageView.image else { return }
let newImage = image.rotate(radians: .pi / 2) // image rotation
YourImageView.image = newImage
} else {
print("IMG VERTICAL")
}
setupConstraints()
}
set up constraints
fileprivate func setupConstraints() {
view.addSubview(YourImageView)
YourImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
YourImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
YourImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
YourImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
}
write image extension
extension UIImage {
func rotate(radians: Float) -> UIImage? {
var newSize = CGRect(origin: CGPoint.zero, size: self.size).applying(CGAffineTransform(rotationAngle: CGFloat(radians))).size
// Trim off the extremely small float value to prevent core graphics from rounding it up
newSize.width = floor(newSize.width)
newSize.height = floor(newSize.height)
UIGraphicsBeginImageContextWithOptions(newSize, false, self.scale)
guard let context = UIGraphicsGetCurrentContext() else { return UIImage()}
// Move origin to middle
context.translateBy(x: newSize.width/2, y: newSize.height/2)
// Rotate around middle
context.rotate(by: CGFloat(radians))
// Draw the image at its center
self.draw(in: CGRect(x: -self.size.width/2, y: -self.size.height/2, width: self.size.width, height: self.size.height))
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
this is the result normal (without image rotate extension call) and rotated (with image rotate extension call):

Transform UIView to UIImage and set it with UIImageView

I refered to this question, and did add extension to UIView:
extension UIView {
// Using a function since `var image` might conflict with an existing variable
// (like on `UIImageView`)
func asImage() -> UIImage {
if #available(iOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}
Then i create very simple test view:
private class FakeTestView: BaseView {
override func prepare() {
backgroundColor = .blue
setup()
}
private func setup(){
let lbl = LabelSL.regular()
lbl.text = "LabelSL.regular()"
addSubview(lbl)
lbl.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
lbl.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
That view showing correctly when treated as UIView.
Finally, i tried:
let newSlideFrame = CGRect(x: CGFloat(i) * event.expectedWidth(),
y: 0,
width: event.expectedWidth(),
height: frame.size.height)
let imgView = UIImageView()
imgView.contentMode = .scaleAspectFit
imgView.frame = newSlideFrame
imgView.image = FakeTestView().asImage()
scroll.addSubview(imgView)
But there is nothing showing. Code from above work when i try to add UIView, or UIImageView with sample images.

inner shadow effect UIView

I want to add inner shadow to this UIlabel
Picture:
reference form Inner shadow effect on UIView layer?
let display = UILabel()
func setBackgroundLayer () {
let backgroundLayer = CAGradientLayer()
backgroundLayer.frame = view.bounds
backgroundLayer.colors = [UIColor(white:1,alpha:0.5).cgColor,UIColor(white:0.5,alpha:0.5).cgColor]
view.layer.insertSublayer(backgroundLayer, at: 0)
}
func setDisplay() {
let Xratio = view.bounds.width/8
display.frame = CGRect(x:Xratio,y:80,width:Xratio*6,height:Xratio*2)
display.backgroundColor = UIColor(red:204/255,green:203/255,blue:181/255,alpha:1)
view.addSubview(display)
}
func displayInnerShadow() {
let shadowLayer = CAShapeLayer()
shadowLayer.frame = view.bounds
let path = CGMutablePath()
path.addRect(display.frame.insetBy(dx: -20, dy: -20))
path.addRect(display.frame)
shadowLayer.fillRule = kCAFillRuleEvenOdd
shadowLayer.path = path
shadowLayer.shadowColor = UIColor(white:0,alpha:1).cgColor
shadowLayer.shadowOffset = CGSize(width:0,height:0)
shadowLayer.shadowOpacity = 1
shadowLayer.shadowRadius = 5
view.layer.insertSublayer(shadowLayer, at: 1)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setBackgroundLayer()
setDisplay()
//displayInnerShadow()
}
I got this result:
Why isn't it working and how to set the fill color?
try this
let display = UILabel()
func setBackgroundLayer () {
let backgroundLayer = CAGradientLayer()
backgroundLayer.frame = view.bounds
backgroundLayer.colors = [UIColor(white:1,alpha:0.5).cgColor,UIColor(white:0.5,alpha:0.5).cgColor]
view.layer.insertSublayer(backgroundLayer, at: 0)
}
func setDisplay() {
let Xratio = view.bounds.width/8
display.frame = CGRect(x:Xratio,y:80,width:Xratio*6,height:Xratio*2)
set layer background color instead of display.backgroundColor
display.layer.backgroundColor = UIColor(red:204/255,green:203/255,blue:181/255,alpha:1).cgColor
view.addSubview(display)
}
func displayInnerShadow() {
let innerShadowLayer = CALayer()
innerShadowLayer.frame = display.bounds
let path = UIBezierPath(rect: innerShadowLayer.bounds.insetBy(dx: -20, dy: -20))
let innerPart = UIBezierPath(rect: innerShadowLayer.bounds).reversing()
path.append(innerPart)
innerShadowLayer.shadowPath = path.cgPath
innerShadowLayer.masksToBounds = true
innerShadowLayer.shadowColor = UIColor.black.cgColor
innerShadowLayer.shadowOffset = CGSize.zero
innerShadowLayer.shadowOpacity = 1
innerShadowLayer.shadowRadius = 5
display.layer.addSublayer(innerShadowLayer)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setDisplay()
setBackgroundLayer()
displayInnerShadow()
}

Add blur view to label?

How can I add a blur view to a label? The label is in front of a UIImage and I wanted the background of the label to be blurred, so that the user can read the text better. I get the Blur effect inside the bounds of the label, but the text itself disappears (maybe also gets blurred, idk why). I also tried to add a label programmatically, but I didn't get it working. I'm thankful for any kind of help!
let blur = UIBlurEffect(style: .Light)
let blurView = UIVisualEffectView(effect: blur)
blurView.frame = findATeamLabel.bounds
findATeamLabel.addSubview(blurView)
You can make your own BlurredLabel which can blur/unblur its text. Through a CoreImage blur filter you take the text of the label, blur it in an image, and display that image on top of the label.
class BlurredLabel: UILabel {
func blur(_ blurRadius: Double = 2.5) {
let blurredImage = getBlurryImage(blurRadius)
let blurredImageView = UIImageView(image: blurredImage)
blurredImageView.translatesAutoresizingMaskIntoConstraints = false
blurredImageView.tag = 100
blurredImageView.contentMode = .center
blurredImageView.backgroundColor = .white
addSubview(blurredImageView)
NSLayoutConstraint.activate([
blurredImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
blurredImageView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
func unblur() {
subviews.forEach { subview in
if subview.tag == 100 {
subview.removeFromSuperview()
}
}
}
private func getBlurryImage(_ blurRadius: Double = 2.5) -> UIImage? {
UIGraphicsBeginImageContext(bounds.size)
layer.render(in: UIGraphicsGetCurrentContext()!)
guard let image = UIGraphicsGetImageFromCurrentImageContext(),
let blurFilter = CIFilter(name: "CIGaussianBlur") else {
UIGraphicsEndImageContext()
return nil
}
UIGraphicsEndImageContext()
blurFilter.setDefaults()
blurFilter.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blurFilter.setValue(blurRadius, forKey: kCIInputRadiusKey)
var convertedImage: UIImage?
let context = CIContext(options: nil)
if let blurOutputImage = blurFilter.outputImage,
let cgImage = context.createCGImage(blurOutputImage, from: blurOutputImage.extent) {
convertedImage = UIImage(cgImage: cgImage)
}
return convertedImage
}
}
PS: Please make sure to improve this component as of your requirements (for example avoid blurring if already blurred or you could remove the current blurred and apply the blurred again if text has changed).
PSPS: Take into consideration also that applying blur to something makes its content bleeds out, so either set clipsToBounds = false to the BlurredLabel or find out other way to accomplish your visual effect in order to avoid the blurred image looks like is not in same position as the label unblurred text that was previously.
To use it you can simply create a BlurredLabel:
let blurredLabel = BlurredLabel()
blurredLabel.text = "56.00 €"
And on some button tap maybe you could achieve blurring as of blurredLabel.blur() and unblurring as of blurredLabel.unblur().
This is the output achieved with blur() through a blurRadius of 2.5:
To read more about Gaussian Blur, there is a good article on Wikipedia: https://en.wikipedia.org/wiki/Gaussian_blur
You could try sending it to the back of the view hierarchy for the label. Try
findATeamLabel.sendSubviewToBack(blurView)
Swift 5 - Blur as UIView extension
extension UIView {
struct BlurableKey {
static var blurable = "blurable"
}
func blur(radius: CGFloat) {
guard superview != nil else { return }
UIGraphicsBeginImageContextWithOptions(CGSize(width: frame.width, height: frame.height), false, 1)
layer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
guard
let blur = CIFilter(name: "CIGaussianBlur"),
let image = image
else {
return
}
blur.setValue(CIImage(image: image), forKey: kCIInputImageKey)
blur.setValue(radius, forKey: kCIInputRadiusKey)
let ciContext = CIContext(options: nil)
let boundingRect = CGRect(
x:0,
y: 0,
width: frame.width,
height: frame.height
)
guard
let result = blur.value(forKey: kCIOutputImageKey) as? CIImage,
let cgImage = ciContext.createCGImage(result, from: boundingRect)
else {
return
}
let blurOverlay = UIImageView()
blurOverlay.frame = boundingRect
blurOverlay.image = UIImage(cgImage: cgImage)
blurOverlay.contentMode = .left
addSubview(blurOverlay)
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
blurOverlay,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
func unBlur() {
guard
let blurOverlay = objc_getAssociatedObject(self, &BlurableKey.blurable) as? UIImageView
else {
return
}
blurOverlay.removeFromSuperview()
objc_setAssociatedObject(
self,
&BlurableKey.blurable,
nil,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
)
}
var isBlurred: Bool {
return objc_getAssociatedObject(self, &BlurableKey.blurable) is UIImageView
}
}
I got it working by adding just a View behind the label (the label is NOT inside that view, just in front of it). Then I added the blur effect to the view... I still think there should be an easier way.
None of the answers worked for me, the accepted answer dont work with background of different color of white, or label color different of black based on the comments it goes to another question yet the answer made the blur move to the right a lot. So after some reading adjusting the CGRect on the result image.
class BlurredLabel: UILabel {
var isBlurring = false {
didSet {
setNeedsDisplay()
}
}
var blurRadius: Double = 8 {
didSet {
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
}
}
lazy var blurFilter: CIFilter? = {
let blurFilter = CIFilter(name: "CIGaussianBlur")
blurFilter?.setDefaults()
blurFilter?.setValue(blurRadius, forKey: kCIInputRadiusKey)
return blurFilter
}()
override init(frame: CGRect) {
super.init(frame: frame)
layer.isOpaque = false
layer.needsDisplayOnBoundsChange = true
layer.contentsScale = UIScreen.main.scale
layer.contentsGravity = .center
isOpaque = false
isUserInteractionEnabled = false
contentMode = .redraw
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func display(_ layer: CALayer) {
let bounds = layer.bounds
guard !bounds.isEmpty && bounds.size.width < CGFloat(UINT16_MAX) else {
layer.contents = nil
return
}
UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.isOpaque, layer.contentsScale)
if let ctx = UIGraphicsGetCurrentContext() {
self.layer.draw(in: ctx)
var image = UIGraphicsGetImageFromCurrentImageContext()?.cgImage
if isBlurring {
blurFilter?.setValue(CIImage(cgImage: image!), forKey: kCIInputImageKey)
let ciContext = CIContext(cgContext: ctx, options: nil)
if let blurOutputImage = blurFilter?.outputImage {
let boundingRect = CGRect(
x:0,
y: blurOutputImage.extent.minY,
width: blurOutputImage.extent.width,
height: blurOutputImage.extent.height
)
image = ciContext.createCGImage(blurOutputImage, from: boundingRect)
}
}
layer.contents = image
}
UIGraphicsEndImageContext()
}
}

Changing Background on orientation change fails

I have two images bg-land and bg-port for different orientation. I wanna change the image on chnaging orientation. But, the image does not change. When I load the Simulator in LandScape, bg-land works perfect but does not work with Rotation. Code:
override func viewDidLoad() {
super.viewDidLoad()
loadBackground()
}
func loadBackground(){
if(UIApplication.sharedApplication().statusBarOrientation.isPortrait){
var bgImage = UIImage(named: "bg-port")
var background = UIImageView(frame: self.view.bounds);
background.image = bgImage
self.view.addSubview(background)
self.view.sendSubviewToBack(background)
println(bgImage)
} else {
var bgImage = UIImage(named: "bg-land")
var background = UIImageView(frame: self.view.bounds);
background.image = bgImage
self.view.addSubview(background)
self.view.sendSubviewToBack(background)
println(bgImage)
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
loadBackground()
}
This code works fine for me try this:
override func viewDidLoad() {
super.viewDidLoad()
self.loadBackground()
}
func loadBackground(){
if(UIApplication.sharedApplication().statusBarOrientation.isPortrait){
let imageName = "Texture.jpg"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = self.view.bounds
view.addSubview(imageView)
} else {
let imageName = "AppIcon.png"
let image = UIImage(named: imageName)
let imageView = UIImageView(image: image!)
imageView.frame = self.view.bounds
view.addSubview(imageView)
}
}
override func didRotateFromInterfaceOrientation(fromInterfaceOrientation: UIInterfaceOrientation) {
loadBackground()
}
Don't forget to change image name from this code.
May be this will help you.
It's not changing because you are sending the newly added UIImageView to the back of your previously created UIImageView. Instead of doing that, create a member variable in your class for UIImageView. (Name it as background)
func loadBackground()
{
if self.background == nil
{
self.background = UIImageView()
self.view.addSubview(self.background)
self.view.sendSubviewToBack(self.background)
}
if(UIApplication.sharedApplication().statusBarOrientation.isPortrait)
{
var bgImage = UIImage(named: "bg-port")
self.background.frame = self.view.bounds;
self.background.image = bgImage
}
else
{
var bgImage = UIImage(named: "bg-land")
self.background.frame = self.view.bounds;
background.image = bgImage
}
}
But it is better to add a UIImageView outlet to your interface and loading the image using code, rather than creating one at run-time. And I prefer that would be a nice approach.

Resources