How to change view for label - swift

I have a UILabel and I want to change its background view. Somebody advised me to use bezier but I want a simple variant.
My label:
What it should look like:

Since these are not very complex shapes, I would suggest creating two white UIViews, rotating them 45 degrees and then adding them as subviews to the UILabel. The code would look something like this:
myLabel.clipsToBounds = true
// create the triangle on the left side of the label
let leftSquare = UIView(frame: CGRect(x: myLabel.frame.height / -2, y: 0, width: myLabel.frame.height, height: myLabel.frame.height))
leftSquare.translatesAutoresizingMaskIntoConstraints = true
leftSquare.isUserInteractionEnabled = false
leftSquare.backgroundColor = UIColor.white
leftSquare.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 4)) // 45 degree rotation
// create the triangle on the right side of the cell
let rightSquare = UIView(frame: CGRect(x: myLabel.bounds.width - (myLabel.frame.height / 2), y: 0, width: myLabel.frame.height, height: myLabel.frame.height))
rightSquare.translatesAutoresizingMaskIntoConstraints = true
rightSquare.isUserInteractionEnabled = false
rightSquare.backgroundColor = UIColor.white
rightSquare.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 4))
// add squares to label
myLabel.addSubview(leftSquare)
myLabel.addSubview(rightSquare)
myLabel.sendSubview(toBack: leftSquare)
myLabel.sendSubview(toBack: rightSquare)

Related

How to make the uiview not move after setting its layer's anchorPoint under Autolayout

I have a requirement that need change the UIView layer's anchorPoint, but the view cannot be moved after changing anchorPoint.
I know it is possible when the view is defined by frame(CGRect:...). like this:
let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
This works.
But my view is defined by Autolayout, I try the solution like above code, but it doesn't work. Code:
let view1 = UIView()
view1.backgroundColor = .orange
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(50)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1
The result is: the orange view1 be moved, it should be like the blue view2 after changing anchorPoint.
So can anyone give me some suggestions?
------------------------------Update Answer-----------------------------
Just as #DonMag answer, we can implement this requirement by updating the constraints of view not frame when using Autolayout. Here is the code by SnapKit:
let view1 = UIView()
view1.backgroundColor = .orange
view1.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(100)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
// important!!!
view1.layoutIfNeeded()
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
// update constraints by updateConstraints function
// if you use #IBOutlet NSLayoutConstraint from xib,
// you can also just set xxx.constant = yyy to update the constraints.
view1.snp.updateConstraints { (maker) in
let subOffset = oldFrame1.width * 0.5
maker.leading.equalToSuperview().offset(20 - subOffset)
maker.trailing.equalToSuperview().offset(-20 - subOffset)
}
let width = SCREEN_WIDTH - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
Another solution is to change the autolayout view to frame by setting translatesAutoresizingMaskIntoConstraints = true, like this:
let width = SCREEN_WIDTH - 40
let view1 = UIView()
view1.backgroundColor = .orange
self.view.addSubview(view1)
// Autolayout
view1.snp.makeConstraints { (maker) in
maker.top.equalToSuperview().offset(100)
maker.leading.equalToSuperview().offset(20)
maker.trailing.equalToSuperview().offset(-20)
maker.height.equalTo(200)
}
// change autolayout to frame
view1.translatesAutoresizingMaskIntoConstraints = true
view1.frame = CGRect(x: 20, y: 100, width: width, height: 200)
let oldFrame1 = view1.frame
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view1.frame = oldFrame1
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
First, when using auto-layout / constraints, setting the view's .frame directly will not give desired results. As soon as auto-layout updates the UI, the constraints will be re-applied.
When you change the .anchorPoint you change the geometry of the view. For that reason, you may be better off using .frame instead of auto-layout.
If you do need / want to use auto-layout, you'll need to update the .constant values of the constraints to account for the geometry changes.
I don't know how to do that with SnapKit, but here is an example using "standard" constraint syntax.
Declare Leading and Trailing constraint variables
assign and activate the constraints
tell auto-layout to calculate the frame
change the anchorPoint
update the Leading and Trailing constraint constants to reflect the geometry change
Note: this is example code only!:
class ViewController: UIViewController {
// these will have their .constant values changed
// to account for layer.anchorPoint change
var leadingConstraint: NSLayoutConstraint!
var trailingConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// NOT using auto-layout constraints
let width = view.frame.width - 40
let view2 = UIView(frame: CGRect(x: 20, y: 300, width: width, height: 200))
view2.backgroundColor = .blue
self.view.addSubview(view2)
let oldFrame2 = view2.frame
view2.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
view2.frame = oldFrame2
// USING auto-layout constraints
let view1 = UIView()
view1.backgroundColor = .orange
view1.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(view1)
// create leading and trailing constraints
leadingConstraint = view1.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0)
trailingConstraint = view1.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0)
// activate constraints
NSLayoutConstraint.activate([
view1.topAnchor.constraint(equalTo: view.topAnchor, constant: 80.0),
view1.heightAnchor.constraint(equalToConstant: 200.0),
leadingConstraint,
trailingConstraint,
])
// auto-layout has not run yet, so force it to layout
// the view frame
view1.layoutIfNeeded()
// get the auto-layout generated frame
let oldFrame1 = view1.frame
// change the anchorPoint
view1.layer.anchorPoint = CGPoint(x: 0, y: 0.5)
// we've move the X anchorPoint from 0.5 to 0.0, so
// we need to adjust the leading and trailing constants
// by 0.5 * the frame width
leadingConstraint.constant -= oldFrame1.width * 0.5
trailingConstraint.constant -= oldFrame1.width * 0.5
}
}
Result:

Central align subView

I'm trying to centre a UIView in another view (so like a pop up view), but whatever I do, I just cannot align it centrally!
func loadPopUpView() {
let customView = CGRect(x: view.center.x, y: view.center.y, width: 100, height: 100)
popUpView = UIView(frame: customView)
popUpView.backgroundColor = UIColor.black
view.addSubview(popUpView)
popUpView.isHidden = false
}
I've changed the background colour to black just so I know when it appears.
I cannot do this with storyboard because it's going on a tableView, so I'm trying to do it programmatically. Result Image
You also need to minus half value of your custom view height and width from x and y like below:
let customView = CGRect(x: view.center.x - 50, y: view.center.y - 50, width: 100, height: 100)
You are telling it to put top left corner in the center, hence you need to let it get half size in position.
let customView = CGRect(x: view.center.x-50, y: view.center.y-50, width: 100, height: 100)
Another solution is the use anchorpoints.
customView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
customView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
customView.heightAnchor.constraint(equalToConstant: 100).isActive = true
customView.widthAnchor.constraint(equalToConstant: 100).isActive = true
On CGRect(x:, y:, width:, height:), the point (x,y) is the origin. In iOS, that's the top left point.
On the CGRect doc:
In the default Core Graphics coordinate space, the origin is located
in the lower-left corner of the rectangle and the rectangle extends
towards the upper-right corner. If the context has a
flipped-coordinate space—often the case on iOS—the origin is in the
upper-left corner and the rectangle extends towards the lower-right
corner.
So to fix this:
let width = 100.0
let height = 100.0
let customViewFrame = CGRect(x: view.center.x - width/2.0, y: view.center.y - height/2.0, width: width, height: height)
Another solution would be to apply the center once the frame (especially the width/size) have been set.
let customViewFrame = CGRect(x: 0, y: 0, width: 100, height: 100)
customViewFrame = view.center

Swift - Setting corner radius on image nested in UIButton

I have a button which contains text and an image but I would like the image to have a corner radius on it. When I try to apply the corner radius to the button, it applies to the whole button which I guess is correct. How can I set the corner radius to the image instead?
Here's my code:
let titleButton = UIButton()
titleButton.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
titleButton.setTitle("display name", for: .normal)
titleButton.setImage(#imageLiteral(resourceName: "imgName"), for: .normal)
titleButton.layer.masksToBounds = true
titleButton.layer.cornerRadius = titleButton.frame.width / 2
self.navigationItem.titleView = titleButton
An UIButton has an UIImageView on it. So to set corner, simply set radius to the layer of the image view
titleButton.imageView?.layer.cornerRadius = 5
if you want rounded then set height and width equal
titleButton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
titleButton.layer.cornerRadius = titleButton.frame.size.width / 2.0
titleButton.layer.masksToBounds = true
And If you want corner rounded only then
titleButton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
titleButton.layer.cornerRadius = 10
titleButton.layer.masksToBounds = true
If you want the cornerRadius to be on the button itself and not the image add the following line:
titleButton.clipsToBounds = true

Rounded rectangle with SKShapeNode

I have the following code that rounds an already existing rectangle from the scene builder...
btOk.path = UIBezierPath(roundedRect: CGRect(x: -62.5,y:-25,width: 125, height: 50), cornerRadius: 10).cgPath
However, if i try to use the same init to round the corners of another rectangle that is much larger, it does not even come close to working. It just makes the width HUUUUGE (imagine Trump).
scene.enumerateChildNodes(withName: "//*"){
node,stop in
if let name = node.name{
if name.contains("round"){
if let shapeNode = node as? SKShapeNode{
print(shapeNode.frame.width) //500
shapeNode.path = UIBezierPath(roundedRect: CGRect(x: -250,y:-100,width: 500, height: 200), cornerRadius: 50).cgPath
print(shapeNode.frame.width) //5735
}
}
}
}
btOk is a SKShapeNode as well. What am i missing between the two that is so different? One thing to note is i am enumerating through the children of the scene like this because this scene is in a SKReferenceNode. Perhaps that has something to do with it?
EDIT
Taking direction from #Ron Myschuk, i've solved this and since it's such a PITA, i also created an extension. So now i can round the corners of any SKShapeNode very easily when needed. Even if it was created in the scene editor. Note, this should only be used if there are no children of the shape node. Otherwise those children will be removed also.
extension SKShapeNode {
func roundCorners(topLeft:Bool,topRight:Bool,bottomLeft:Bool,bottomRight:Bool,radius: CGFloat,parent: SKNode){
let newNode = SKShapeNode(rect: self.frame)
newNode.fillColor = self.fillColor
newNode.lineWidth = self.lineWidth
newNode.position = self.position
newNode.name = self.name
self.removeFromParent()
parent.addChild(newNode)
var corners = UIRectCorner()
if topLeft { corners = corners.union(.bottomLeft) }
if topRight { corners = corners.union(.bottomRight) }
if bottomLeft { corners = corners.union(.topLeft) }
if bottomRight { corners = corners.union(.topRight) }
newNode.path = UIBezierPath(roundedRect: CGRect(x: -(newNode.frame.width / 2),y:-(newNode.frame.height / 2),width: newNode.frame.width, height: newNode.frame.height),byRoundingCorners: corners, cornerRadii: CGSize(width:radius,height:radius)).cgPath
}
}
And use it like so...
aShapeNode.roundCorners(topLeft: true, topRight: true, bottomLeft: false, bottomRight: false, radius: 5,parent: popup)
Not what you're going to want to hear but it's because you cannot set the width of an SKShapeNode in the Scene editor (To my knowledge). In order to get that ShapeNode to have a width of 500 you would have had to adjust the xScale. The xScale then reapplies itself to the path when you adjust it (kind of growing exponentially). If you create the SKShapeNode in code there is no problem adjust the rounded corners
let round = SKShapeNode(rect: CGRect(x: 0, y: -150, width: 500, height: 200))
round.fillColor = .red
addChild(round)
print(round.frame.width)
round.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(round.frame.width)
Edit
If you have your heart set on using the Scene editor you can place your ShapeNode and stretch it to where you want it then you could just do a small conversion in code to get the results that you want
if let round = self.childNode(withName: "biground") as? SKShapeNode {
let biground = SKShapeNode(rect: round.frame)
biground.fillColor = round.fillColor
biground.position = round.position
addChild(biground)
round.removeFromParent()
print(biground.frame.width)
biground.path = UIBezierPath(roundedRect: CGRect(x: -250, y: -100, width: 500, height: 200), cornerRadius: 50).cgPath
print(biground.frame.width)
}
this just recreates the shape in code based on what you outlined in the Scene editor and rounds the edges perfectly
edit 2
I've always been under the impression that SKShapeNodes are really inefficient (i'm pretty sure they leaked memory as well). So i always setup my round rectangles as so.
let outsideTexture = SKTexture(imageNamed: "round_square_white")
let outside = SKSpriteNode(texture: outsideTexture)
let insetX: CGFloat = 20
let insetY: CGFloat = 20
let cellSize = CGSize(width: 500.0, height: 500.0)
outside.centerRect = CGRect(x: CGFloat(insetX / outside.size.width), y: CGFloat(insetY / outside.size.height), width: CGFloat((outside.size.width - insetX * 2) / outside.size.width), height: CGFloat((outside.size.height - insetY * 2) / outside.size.height))
//outside.position = CGPointMake(gameModel.gameWidth / 2, (outside.size.height) / 2);
outside.xScale = cellSize.width / outside.size.width
outside.yScale = cellSize.height / outside.size.height
outside.zPosition = 10
outside.position = CGPoint(x: CGFloat(0), y: CGFloat(-0.35 * self.size.height / 2))
self.addChild(outside)
worth noting that this lays out a rounded square/rectangle perfectly however similar to the scale issues from the scene editor you have to place an empty cell over this to add children to, otherwise they scale to the rounded square.

Shadow left and right of UIView

Hi i like to add an CALayer Shadow to an View where the shadow was left and right of the view the simplest way was:
someView.layer.shadowColor = [[UIColor blackColor] CGColor];
someView.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
someView.layer.shadowOpacity = 1.0f;
someView.layer.shadowRadius = 10.0f;
someView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:someView.bounds] CGPath];
but i will add a bigger shadow as like a shadow when i increase the shadowRadius it do not look good. How i can make a shadow that looks good at left and right.
I think 10 is a pretty big shadow radius, try 3 or 4 instead, also opacity I usually use 0.7:
someView.layer.shadowColor = [[UIColor blackColor] CGColor];
someView.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
someView.layer.shadowOpacity = 0.7f;
someView.layer.shadowRadius = 4.0f;
If you want the shadow only on left and right, then inset the rectangle on the top and bottom so the top and bottom shadow are hidden behind your view:
CGRect shadowRect = CGRectInset(someView.bounds, 0, 4); // inset top/bottom
someView.layer.shadowPath = [[UIBezierPath bezierPathWithRect:shadowRect] CGPath];
I'm not really sure if that's what you wanted.
swift 3.0 version
imageView.layer.shadowOpacity = 0.8
imageView.layer.shadowOffset = CGSize(width: 0, height: 3)
imageView.layer.shadowRadius = 4.0
let shadowRect: CGRect = imageView.bounds.insetBy(dx: 0, dy: 4)
imageView.layer.shadowPath = UIBezierPath(rect: shadowRect).cgPath
progrmr's answer was very helpful, cheers!
I made a slide-out menu and I had a problem with the shadow surrounding my VC and disrupting the navigation bar. Turned out I had to inset the shadow layer.
Here's my solution using swift:
rightViewController!.view.layer.shadowOpacity = 0.8
rightViewController!.view.layer.shadowOffset = CGSizeMake(0, 3)
rightViewController!.view.layer.shadowRadius = 4.0
let shadowRect: CGRect = CGRectInset(rightViewController!.view.bounds, 0, 4); // inset top/bottom
rightViewController!.view.layer.shadowPath = UIBezierPath(rect: shadowRect).CGPath
I created a shadow that can be used at the top/bottom or left/right, without prejudice at all. No shadow will appear in the left/right if you choose top/bottom, and so on. I hope I helped. Here's the code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let imgView = UIImageView(frame: CGRect(x: self.view.bounds.midX - 150, y: self.view.bounds.midY - 150, width: 300, height: 300))
imgView.contentMode = .scaleToFill
imgView.image = UIImage(named: "name image")
self.view.addSubview(imgView)
//You can put left/right or top/bottom
imgView.addShadow(sides: .leftRight, constant: 10, alpha: 5)
}
}
extension UIView{
func addShadow(sides: Sides, constant: Int, alpha: Int){
if(constant > 0 && alpha > 0){
switch sides {
case .leftRight:
let view = UIView(frame: CGRect(x: -CGFloat(constant), y: 0, width: self.bounds.width + CGFloat((constant * 2)), height: self.bounds.height))
self.addSubview(view)
addMask(sides: .leftRight, view: view, constant: constant, shadowRadius: alpha)
case .topBottom:
let view = UIView(frame: CGRect(x: 0, y: -CGFloat(constant), width: self.bounds.width , height: self.bounds.height + CGFloat(constant * 2)))
self.addSubview(view)
addMask(sides: .topBottom, view: view, constant: constant, shadowRadius: alpha)
}
}
}
private func addMask(sides:Sides, view: UIView, constant: Int, shadowRadius:Int){
let mutablePath = CGMutablePath()
let mask = CAShapeLayer()
switch sides {
case .leftRight:
mutablePath.addRect(CGRect(x: -CGFloat(constant), y: 0, width: view.bounds.width, height: view.bounds.height))
mutablePath.addRect(CGRect(x: CGFloat(constant) , y: 0, width: view.bounds.width , height: view.bounds.height))
case .topBottom:
mutablePath.addRect(CGRect(x: 0, y: -CGFloat(constant), width: view.bounds.width, height: view.bounds.height))
mutablePath.addRect(CGRect(x: 0, y: CGFloat(constant) , width: view.bounds.width , height: view.bounds.height))
}
mask.path = mutablePath
mask.fillRule = CAShapeLayerFillRule.evenOdd
mask.fillColor = UIColor(white:1.0, alpha: 0.2).cgColor
view.layer.mask = mask
view.layer.addSublayer(mask)
mask.shadowColor = UIColor.black.cgColor
mask.shadowRadius = CGFloat(shadowRadius)
mask.shadowOpacity = 1.0
mask.shadowOffset = CGSize(width: 0, height: 0)
}
}
enum Sides{
case leftRight
case topBottom
}
You just need to call the extension UIView, which has the addShadow() function and pass the parameters you want.