Custom UIViews For Collage App - ios

I am working on a collage application, and I have successfully build the simple collages but now I am stuck on how to build complex style collages like in the following image
How I will be able to create the custom view like in the image, I have tried using Beizerpath and by applying masking to the view with the path of the beizerpath, but I am not able to add shadow to the layer when I apply the mask to the view. Any help would be appreciated. How can I exactly create the type of view in the image with the shadow behind them?

Asking for somebody to tell you exactly how to implement your app's features is not appropriate for this site.
Look a using CAShapeLayers and setting their various shadow properties, like shadowOpacity, shadowRadius, shadowOffset, shadowColor, and shadowPath.

Use this code
import UIKit
class ViewController: UIViewController {
#IBOutlet var image1: UIImageView! // your image outlet
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(_ animated: Bool) {
let trianglePath = UIBezierPath()
trianglePath.move(to: CGPoint(x: 0, y: image1.frame.size.height - 60))
trianglePath.addLine(to: CGPoint(x:0, y: image1.frame.size.height))
trianglePath.addLine(to: CGPoint(x: image1.frame.size.width, y: image1.frame.size.height))
trianglePath.close()
let path = UIBezierPath(rect: view.bounds)
path.append(trianglePath)
let maskLayer = CAShapeLayer()
maskLayer.frame = image1.bounds
maskLayer.fillRule = kCAFillRuleEvenOdd
maskLayer.path = path.cgPath
image1.layer.mask = maskLayer
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And more shapes link appcoda

Related

how to give a dash border to a rectangular uiview in swift

I have all ready tried tried
extension UIView {
#discardableResult
func addLineDashedStroke(pattern: [NSNumber]?, radius: CGFloat, color: CGColor) -> CALayer {
let borderLayer = CAShapeLayer()
borderLayer.strokeColor = color
borderLayer.lineDashPattern = pattern
borderLayer.frame = bounds
borderLayer.fillColor = nil
borderLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
layer.addSublayer(borderLayer)
return borderLayer
}
}
I am not able to figure out the use of pattern?
That extension adds a function to UIView that creates and returns a dashed border CAShapeLayer. It doesn't do anything with the layer. If you just call the function and don't do anything with the layer it returns, you won't see any change.
I was able to use the extension in your question to add a dashed, rounded-corner border around a view quite easily:
Here is a custom subclass of UIViewController I implemented in a test app (I added a UIView in the storyboard and linked it to the IBOutlet dashedBorderView:
class ViewController: UIViewController {
#IBOutlet weak var dashedBorderView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
let dashedBorderLayer = dashedBorderView.addLineDashedStroke(pattern: [2, 5], radius: 10, color: UIColor.black.cgColor)
dashedBorderView.layer.addSublayer(dashedBorderLayer)
}
}
That code creates a bordered view that looks like this:

How to create the below attached UI using CG classes in Swift?

How to create this kind of UI?
So far, I end up creating the same UI like below.
I am not sure, how to curve that yellow colored border as in the above reference.
Simply adding a border layout and adding a mask you can achieve what you need
Full Example (only relevant code)
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var selectedIndex : Int = -1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tableView.layer.borderColor = UIColor.red.cgColor
self.tableView.layer.borderWidth = 3
}
func bezierPathWithShape(rect:CGRect,cornerRadius:CGFloat) ->UIBezierPath
{
let path = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
return path
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let mask = CAShapeLayer(layer: self)
mask.path = self.bezierPathWithShape(rect: self.tableView.bounds, cornerRadius: 15).cgPath
self.tableView.layer.mask = mask
self.tableView.layer.masksToBounds = true
}
}
Result
Here is another way to do it.
Take a view which is superView to both the tableView and the lines.
Add cornerRadius, then set clipToBounds = true for the superView.
It will clip the lines according to the cornerRadius.

Delete border of UIView when app changes size class

I have a blue view with a white dashed border. As you can see from the image below, once the app is rotated the view changes its dimensions and the border doesn't adjust to the view's new width and height.
I need to find a way to
Know when the view controller changes its size - perhaps using viewWillTransitionToSize.
Delete the previously drawn border, if any.
Add a new border to the view - in the view's drawRect method.
How can I delete the border previously drawn on the view?
#IBOutlet weak var myView: UIView!
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animateAlongsideTransition(nil, completion: {
_ in
self.myView.setNeedsDisplay()
})
}
class RenderView: UIView {
override func drawRect(rect: CGRect) {
self.addDashedBorder()
}
}
extension UIView {
func addDashedBorder() {
let color = UIColor.whiteColor().CGColor
let shapeLayer:CAShapeLayer = CAShapeLayer()
let frameSize = self.frame.size
let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
shapeLayer.bounds = shapeRect
shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
shapeLayer.fillColor = UIColor.clearColor().CGColor
shapeLayer.strokeColor = color
shapeLayer.lineWidth = 6
shapeLayer.lineJoin = kCALineJoinRound
shapeLayer.lineDashPattern = [6,3]
shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 5).CGPath
self.layer.addSublayer(shapeLayer)
}
}
Download sample project here.
This is not the right way to do things. Each time your RenderView is redrawn, its drawRect method will be called, and addDashedBorder will add a new layer to your view.
drawRect is for drawing inside of your view using CoreGraphics or UIKit, not CoreAnimation. Inside that method, you should just draw, nothing else. If you want to use it a layer, layoutSubviews is a better place to add it, and to update it to match the view.
Here are two ways to solve your problem. Both update the border correctly, and animate the border smoothly when the device is rotated.
Alternative 1: Just draw the border in drawRect, rather than using a separate shape layer. Also, set your view's contentMode so it automatically redraws when its size changes.
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.myView.contentMode = .Redraw
}
}
class RenderView: UIView {
override func drawRect(rect: CGRect) {
let path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 5)
path.lineWidth = 6
let pattern: [CGFloat] = [6.0, 3.0]
path.setLineDash(pattern, count: 2, phase: 0)
UIColor.whiteColor().setStroke()
path.stroke()
}
}
Alternative 2: Continue to use CAShapeLayer, but only create a single one, by using a lazy stored property. Update the layer in an override of layoutSubviews, and if necessary, animate it alongside any changes in the view's bounds.
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
}
class RenderView: UIView {
// Create the borderLayer, and add it to our view's layer, on demand, only once.
lazy var borderLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = nil
shapeLayer.strokeColor = UIColor.whiteColor().CGColor
shapeLayer.lineWidth = 6
shapeLayer.lineDashPattern = [6,3]
self.layer.addSublayer(shapeLayer)
return shapeLayer
}()
override func layoutSubviews() {
super.layoutSubviews()
// We will update the borderLayer's path to match the view's current bounds.
let newPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: 5).CGPath
// We may be animating from the old bounds to the new bounds.
// If so, we want the borderLayer to animate alongside that.
// (UIView does not do this automatically, since it does not know
// anything about our borderLayer; it's at the the CoreAnimation level,
// below UIKit.)
//
// We want an animation that uses the same properties as the existing
// animation, but applies to a different value: the borderLayer's path.
// We'll find the existing animation on the view's bounds.size,
// and if it exists, add our own animation based on it that will
// apply the path change.
if let viewBoundsAnimation = self.layer.animationForKey("bounds.size") {
let pathAnimation = CABasicAnimation()
pathAnimation.beginTime = viewBoundsAnimation.beginTime
pathAnimation.duration = viewBoundsAnimation.duration
pathAnimation.speed = viewBoundsAnimation.speed
pathAnimation.timeOffset = viewBoundsAnimation.timeOffset
pathAnimation.timingFunction = viewBoundsAnimation.timingFunction
pathAnimation.keyPath = "path"
pathAnimation.fromValue = borderLayer.path
pathAnimation.toValue = newPath
borderLayer.addAnimation(pathAnimation, forKey: "path")
}
// Finally, whether we are animating or not, make the border layer show the new path.
// If we are animating, this will appear when the animation is finished.
// If we are not animating, this will appear immediately.
self.borderLayer.path = newPath
}
}
Note that neither of these alternatives require overriding viewWillTransitionToSize or traitCollectionDidChange. Those are higher-level UIViewController concepts, that may get called during device rotation, but won't happen if some other code changes your view's size. It's better to use the simple UIView-level drawRect or layoutSubviews methods, because they will always work.
Call set needs display when the view transitions.You can detect it by using this function.It works perfectly to detect orientation change
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
self.myView.setNeedsDisplay()
}

How To Create in Swift a Circular Profile Picture or Rounded Corner Image with a border which does not leak?

Basing on the source code below:
#IBOutlet var myUIImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.makingRoundedImageProfileWithRoundedBorder()
}
private func makingRoundedImageProfileWithRoundedBorder() {
// Making a circular image profile.
// self.myUIImageView.layer.cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded image profile.
self.myUIImageView.layer.cornerRadius = 20.0
self.myUIImageView.clipsToBounds = true
// Adding a border to the image profile
self.myUIImageView.layer.borderWidth = 10.0
self.myUIImageView.layer.borderColor = UIColor.whiteColor().CGColor
}
Indeed I am able to render a circular or rounded UIImageView, but the problem is that if we add the border, the image leaks a bit. It's way worse with a circular UIImageView, it leaks whenever the border is bent, so LEAKS EVERYWHERE! You can find a screenshot of the result below:
Any way to fix that in Swift? Any sample code which answers to this question will be highly appreciated.
Note: as far as possible the solution has to be compatible with iOS 7 and 8+.
First Solution
Basing on the #Jesper Schläger suggestion
"If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0."
Please find the Swift implementation below:
import UIKit
class ViewController: UIViewController {
#IBOutlet var myUIImageView: UIImageView!
#IBOutlet var myUIViewBackground: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Making a circular UIView: cornerRadius = self.myUIImageView.frame.size.width / 2
// Making a rounded UIView: cornerRadius = 10.0
self.roundingUIView(self.myUIImageView, cornerRadiusParam: 10)
self.roundingUIView(self.myUIViewBackground, cornerRadiusParam: 20)
}
private func roundingUIView(let aView: UIView!, let cornerRadiusParam: CGFloat!) {
aView.clipsToBounds = true
aView.layer.cornerRadius = cornerRadiusParam
}
}
Second Solution
Would be to set a circle mask over a CALayer.
Please find the Objective-C implementation of this second solution below:
CALayer *maskedLayer = [CALayer layer];
[maskedLayer setFrame:CGRectMake(50, 50, 100, 100)];
[maskedLayer setBackgroundColor:[UIColor blackColor].CGColor];
UIBezierPath *maskingPath = [UIBezierPath bezierPath];
[maskingPath addArcWithCenter:maskedLayer.position
radius:40
startAngle:0
endAngle:360
clockwise:TRUE];
CAShapeLayer *maskingLayer = [CAShapeLayer layer];
[maskingLayer setPath:maskingPath.CGPath];
[maskedLayer setMask:maskingLayer];
[self.view.layer addSublayer:maskedLayer];
If you comment out from line UIBezierPath *maskingPath = [UIBezierPath bezierPath]; through [maskedLayer setMask:maskingLayer]; you will see that the layer is a square. However when these lines are not commented the layer is a circle.
Note: I neither tested this second solution nor provided the Swift implementation, so feel free to test it and let me know if it works or not through the comment section below. Also feel free to edit this post adding the Swift implementation of this second solution.
If I may suggest a quick and dirty solution:
Instead of adding a border to the image view, you could just add another white view below the image view. Make the view extend 10 points in either direction and give it a corner radius of 20.0. Give the image view a corner radius of 10.0.
I worked on improving the code but it keeps crashing. I'll work on it, but I appear to have got a (rough) version working:
Edit Updated with a slightly nicer version. I don't like the init:coder method but maybe that can factored out/improved
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
var cornerRadius: CGFloat?
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
image.drawInRect(rect)
let cornerRadius = self.cornerRadius ?? rect.size.width/10
let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
UIColor.whiteColor().setStroke()
path.lineWidth = cornerRadius
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
Let me know if that's the sort of thing you're looking for.
A little explanation:
As I'm sure you've found, the "border" that iOS can apply isn't perfect, and shows the corners for some reason. I found a few other solutions but none seemed to work. The reason this is a subclass of UIView, and not UIImageView, is that drawRect: is not called for subclasses of UIImageView. I am not sure about the performance of this code, but it seems good from my (limited) testing
Original code:
class RoundedImageView: UIView {
var image: UIImage? {
didSet {
if let image = image {
self.frame = CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
}
}
private class func frameForImage(image: UIImage) -> CGRect {
return CGRect(x: 0, y: 0, width: image.size.width/image.scale, height: image.size.width/image.scale)
}
override func drawRect(rect: CGRect) {
if let image = self.image {
self.image?.drawInRect(rect)
let path = UIBezierPath(roundedRect: rect, cornerRadius: 50)
UIColor.whiteColor().setStroke()
path.lineWidth = 10
path.stroke()
}
}
}
let image = UIImage(named: "big-teddy-bear.jpg")
let imageView = RoundedImageView()
imageView.image = image
imageView.layer.cornerRadius = 50
imageView.clipsToBounds = true

Failing to stroke() the UIBezierPath

I wish to create a perfectly rounded rect (Circle) and paint it on the screen. I have tested my code in playground, and it successfully paints the UIBezierPath. It doesn't, however, successfully paint it in the iOS simulator. Here's the code I've been working on:
class Circles {
//Defining the rounded rect (Circle)
func roundRect(radius: CGFloat, angle: CGFloat) -> UIBezierPath {
//Creating the rounded the rectangle (Circle)
var roundedRect = UIBezierPath()
roundedRect.addArcWithCenter(CGPointZero, radius: radius,
startAngle: 0, endAngle: angle ,
clockwise: true)
return roundedRect
}
//Drawing the Bezier Path (Circle)
func drawRect(rect: UIBezierPath){
rect.moveToPoint(self.point)
UIColor.blackColor().setStroke()
rect.stroke()
}
//Giving the rounded rect (Circle) it's position
var point = CGPointMake(500, 500)
}
//Giving the rounded rect (Circle) it's size
var newRect = Circles().roundRect(200.0, angle: 7)
//Drawing the rounded rect (Circle)
Circles().drawRect(newRect)
I have seen some other posts with similar problems from a few years back, however they were in Objective-C, I tried translating but it was not of any use. I've also tried several other methods of painting the path on the screen but, again sadly, it was of no use. I tested it to make sure the functions are working with println statements, the issue is I don't know why the stroke is not activating. Thanks for reading, -Zach.
Here's the updated version using what Mike said:
class CircleView: UIView {
override func drawRect(rect: CGRect) {
// Creating the rectangle's size
var newRect = Circles().roundRect(200.0, angle: 7)
//Drawing the rectangle
Circles().drawRect(newRect)
}
//Holding all to do with the circle
class Circles {
//Defining the rounded rect (Circle)
func roundRect(radius: CGFloat, angle: CGFloat) -> UIBezierPath {
//Creating the rounded rect (Circle)
var roundedRect = UIBezierPath()
roundedRect.addArcWithCenter(CGPointZero, radius: radius,
startAngle: 0, endAngle: angle ,
clockwise: true)
return roundedRect
}
//Drawing the Bezier Path (Circle)
func drawRect(rect: UIBezierPath){
rect.moveToPoint(self.point)
UIColor.blackColor().setStroke()
UIColor.blackColor().setFill()
rect.stroke()
rect.fill()
}
//Giving the rounded rect (Circle) it's position
var point = CGPointMake(500, 500)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//Generating the background
self.view.backgroundColor = UIColor(patternImage: UIImage(named: "normalpaper.jpg"))
let circleView = CircleView(frame: self.view.bounds)
self.view.addSubview(circleView)
}
As you mentioned in the comments, you're calling drawRect from a UIViewController's viewDidLoad function. You don't have a valid drawing context there, so that's not going to work.
The easiest way to make this work is to create a UIView subclass with its own drawRect function which calls Circle().drawRect(...) and add that as a subview of your UIViewController's view.
Here's an example of this:
Note: I've made the custom view transparent by setting circleView.opaque = false so that the background you mentioned in the comments shows through.
class CircleView: UIView {
override func drawRect(rect: CGRect) {
// I just copied this directly from the original question
var newRect = Circles().roundRect(200.0, angle: 7)
Circles().drawRect(newRect)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Create a new CircleView that's the same size of this
// view controller's bounds and add it to the view hierarchy
let circleView = CircleView(frame: self.view.bounds)
circleView.opaque = false
self.view.addSubview(circleView)
}
}
Note: If you're going to be doing custom drawing, I highly recommend you read Drawing and Printing Guide for iOS. It'll teach you all the basics you need to make it work.

Resources