Swift iOS UITabBar Customization - ios

I have a tab bar. I changed its colour, gave it a corner radius, set its border width and colour, and everything is working good. Until now, everything is done in a storyboard.
Now I want to give it left and right margins, as by default it is sticking to the screen's edges.
This is the tab bar's current look:
The black arrows point to the lines that stick to the screen's edges. I want space between this line and the edges.

You can create this placeholder view inside a custom UITabBar as below,
class CustomTabBar: UITabBar {
let roundedView = UIView(frame: .zero)
override func awakeFromNib() {
super.awakeFromNib()
roundedView.layer.masksToBounds = true
roundedView.layer.cornerRadius = 12.0
roundedView.layer.borderWidth = 2.0
roundedView.isUserInteractionEnabled = false
roundedView.layer.borderColor = UIColor.black.cgColor
self.addSubview(roundedView)
}
override func layoutSubviews() {
super.layoutSubviews()
let margin: CGFloat = 12.0
let position = CGPoint(x: margin, y: 0)
let size = CGSize(width: self.frame.width - margin * 2, height: self.frame.height)
roundedView.frame = CGRect(origin: position, size: size)
}
}
Set this class for TabBar in storyboard/xib. It should give you the following,

Related

How can I get the coordinates of a Label inside a view?

What I am trying to do is to get the position of my label (timerLabel) in order to pass those coordinates to UIBezierPath (so that the center of the shape and the center of the label coincide).
Here's my code so far, inside the viewDidLoad method, using Xcode 13.2.1:
// getting the center of the label
let center = CGPoint.init(x: timerLabel.frame.midX , y: timerLabel.frame.midY)
// drawing the shape
let trackLayer = CAShapeLayer()
let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.lightGray.cgColor
trackLayer.lineWidth = 10
trackLayer.fillColor = UIColor.clear.cgColor
and this is what I have when I run my app:
link
What I don't understand is why I get (0,0) as coordinates even though I access the label's property (timerLabel.frame.midX).
The coordinates of your label may vary depending on current layout. You need to track all changes and reposition your circle when changes occur. In view controller that uses constraints you would override
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// recreate your circle here
}
this alone does not explain why your circle is so far out. First of all, looking at your image you do not get (0, 0) but some other value which may be relative position of your label within the blue bubble. The frame is always relative to its superview so you need to convert that into your own coordinate system:
let targetView = self.view!
let sourceView = timerLabel!
let centerOfSourceViewInTargetView: CGPoint = targetView.convert(CGPoint(x: sourceView.bounds.midX, y: sourceView.bounds.midY), to: targetView)
// Use centerOfSourceViewInTargetView as center
but I suggest using neither of the two. If you are using constraints (which you should) then rather create more views than adding layers to your existing views.
For instance you could try something like this:
#IBDesignable class CircleView: UIView {
#IBInspectable var lineWidth: CGFloat = 10 { didSet { refresh() } }
#IBInspectable var strokeColor: UIColor = .lightGray { didSet { refresh() } }
override var frame: CGRect { didSet { refresh() } }
override func layoutSubviews() {
super.layoutSubviews()
refresh()
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let fillRadius: CGFloat = min(bounds.width, bounds.height)*0.5
let strokeRadius: CGFloat = fillRadius - lineWidth*0.5
let path = UIBezierPath(ovalIn: .init(x: bounds.midX-strokeRadius, y: bounds.midY-strokeRadius, width: strokeRadius*2.0, height: strokeRadius*2.0))
path.lineWidth = lineWidth
strokeColor.setStroke()
UIColor.clear.setFill() // Probably not needed
path.stroke()
}
private func refresh() {
setNeedsDisplay() // This is to force redraw
}
}
this view should draw your circle within itself by overriding draw rect method. You can easily use it in your storyboard (first time it might not draw in storyboard because Xcode. Simply close your project and reopen it and you should see the circle even in storyboard).
Also in storyboard you can directly modify both line width and stroke color which is very convenient.
About the code:
Using #IBDesignable to see drawing in storyboard
Using #IBInspectable to be able to set values in storyboard
Refreshing on any value change to force redraw (sometimes needed)
When frame changes forcing a redraw (Needed when setting frame from code)
A method layoutSubviews is called when resized from constraints. Again redrawing.
Path is computed so that it fits within the size of view.

How can i drop shadow to top and bottom of view which is inside the table view cell?

I have a view inside the table view cell and I want to show shadow to that view if below situations
if view on first cell then drop shadow to top, left, right.
if view on last cell then drop shadow to bottm, left, right.
other wise drop shadow only on left and right.
Thank You.
yourView.layer.shadowColor = UIColor.black.cgColor
yourView.layer.shadowOffset = CGSize(width: 0.0, height: 0.0)
yourView.layer.shadowOpacity = 0.5
yourView.layer.shadowRadius = 2.0
And adapt the code below to the way you want for the corners that you'd like to drop shadow.
var shadowRect: CGRect = yourView.bounds.insetBy(dx: 0, dy: 4) // inset top and bottom of the cell
yourView.layer.shadowPath = UIBezierPath(rect: shadowRect).cgPath
put this code in your class
extension UIView {
func dropShadow(scale: Bool = true) {
layer.masksToBounds = false
layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.2
layer.shadowOffset = CGSize.zero
layer.shadowRadius = 5
}}
Then call it:--
YourView.dropShadow(scale: true)

Change the height of UISegmented Control using IB?

How can i change the height of UISegmented control. I'm using Swift 3.0 with xcode 8. Height property is disabled by default.
I found this:
https://stackoverflow.com/a/41889155/7652057
#IBDesignable class MySegmentedControl: UISegmentedControl {
#IBInspectable var height: CGFloat = 29 {
didSet {
let centerSave = center
frame = CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: height)
center = centerSave
}
}
}
https://stackoverflow.com/a/37716960/7652057
One of three options from the link,
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let rect = CGRect(origin: segment.frame.origin, size: CGSize(width: segment.frame.size.width, height: 100))
segment.frame = rect
}
Ok, I was trying to do it for a long while and here is the solution.
Firstly, It is possible within IB but for that we need to have a good bunch of autolayout constraints.
I've placed that Segmented control in a UIVIew with all the edges pinned inside it.
Then I gave the desired height to that view and it worked.
Also.. thanks to all of your answer
It's very easy. You can access it programmaticly by using frame's height:
yourSegmentedControllOutlet.frame.size.height = yourHeight

How to tile an image from center using UIColor patternImage in Swift

I have a method for repeating or tiling an image across a view using UIColor patternImage:
view.backgroundColor = UIColor(patternImage: UIImage(named:imageName))
The default behaviour of UIColor patternImage is to start the pattern from the top left, the result shown in Image 1.
Question:
How can I get UIColor patternImage to start always from the very center of a view, patterning outwards to give the result shown in Image 2?
This works. It is a generalised method that applies more broadly, adapting dynamically to both pattern images and views of different heights and widths. It gives the desired result in the question and is tested on different iOS simulators.
view.backgroundColor = UIColor(patternImage: UIImage(named: "imageName")!)
view.bounds.origin.x = (UIImage(named: "imageName")!.size.width/2) - (view.bounds.size.width/2)
view.bounds.origin.y = (UIImage(named: "imageName")!.size.height/2) - (view.bounds.size.height/2)
If your image size is 50*50 then you can do something like this,
myView = UIVIew(frame: CGRect(x: (self.view.frame.size.width/2)-25, y: (self.view.frame.size.height/2)-25, width: 50, height: 50))
So this view(imageview in yourcase may be) will be place at exact middle of view. Like wise you can arrange other view by adding or substracting view's width and height to center view's x and y origin. Hope this will help you. :)
I created reusable class PatternView, reusing idea of #user4806509. It's really drop-in component for showing patterns. Also, patternImage can be set via Interface Builder
class PatternView: UIView {
#IBInspectable var patternImage: UIImage?
override func awakeFromNib() {
super.awakeFromNib()
self.setupBackgroundColor()
}
private func setupBackgroundColor() {
guard let patternImage = self.patternImage else {
return
}
self.backgroundColor = UIColor(patternImage: patternImage)
}
override func layoutSubviews() {
super.layoutSubviews()
self.centerPattern()
}
private func centerPattern() {
guard let patternSize = self.patternImage?.size,
patternSize.width > 0, patternSize.height > 0 else {
return
}
let x = -self.bounds.width.remainder(dividingBy: patternSize.width)/2
let y = -self.bounds.height.remainder(dividingBy: patternSize.height)/2
self.bounds.origin = CGPoint(x: x, y: y)
}}

Swift: Producing a circle UIView is giving a diamond like shape

I created an extension on UIView so that I can create circle views easily without writing the code in each custom component. My code looks as:
extension UIView {
func createCircleView(targetView: UIView) {
let square = CGSize(width: min(targetView.frame.width, targetView.frame.height), height: min(targetView.frame.width, targetView.frame.height))
targetView.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: square)
targetView.layer.cornerRadius = square.width / 2.0
}
}
the purpose of the property square is to always compute a perfect square based on the smallest property of width or height from the target view, this stops rectangles from trying to become squares, as that could obviously never produce a circle.
Inside my custom component I call this method with:
// machineCircle is a child view of my cell
#IBOutlet weak var machineCircle: UIView!
// Whenever data is set, update the cell with an observer
var machineData: MachineData? {
didSet {
createCircleView(machineCircle)
}
}
The problem I am having is that my circles are rendering to the screen like this:
When debugging, I inspected the square variable, it consistently prints width: 95, height: 95, which would lead me to believe that a perfect circle should be rendered each time.
Why am I seeing these strange shapes?
UPDATE I have found why perfect circles aren't being formed but I am not sure how to go about it.
In my storyboard I set the default size of my machineCircle view to be 95x95, however when my view loads, the collection cells width and height are computed dynamically with this method:
override func viewDidLoad() {
super.viewDidLoad()
let width = CGRectGetWidth(collectionView!.frame) / 3
let layout = collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: width, height: width + width / 2)
}
This resizes the collection view cells so that they can fit in cols of 3 accross the screen, but it does not seem to change the base scale of the inner machineCircle view. The machineCircle view still retains its size of 95x95 but seems to scale down inside the view causing the effect to be caused (thats what I have observed thus far). Any ideas?
Taking Matt's advice, I created a method to draw a circle within a UIView using CALayers.
For any who are interested, here is my implementation:
func drawCircleInView(parentView: UIView, targetView: UIView, color: UIColor, diameter: CGFloat)
{
let square = CGSize(width: min(parentView.bounds.width, parentView.bounds.height), height: min(parentView.bounds.width, parentView.bounds.height))
let center = CGPointMake(square.width / 2 - diameter, square.height / 2 - diameter)
let circlePath = UIBezierPath(arcCenter: center, radius: CGFloat(diameter), startAngle: CGFloat(0), endAngle: CGFloat(M_PI * 2), clockwise: true)
let shapeLayer = CAShapeLayer()
print(targetView.center)
shapeLayer.path = circlePath.CGPath
shapeLayer.fillColor = color.CGColor
shapeLayer.strokeColor = color.CGColor
shapeLayer.lineWidth = 1.0
targetView.backgroundColor = UIColor.clearColor()
targetView.layer.addSublayer(shapeLayer)
}
And it is called with:
drawCircleInView(self, machineCircle, color: UIColor.redColor(), radius: 30)
Here is the result:
The white box behind is for demonstration purposes only, it shows the parent view that the circle is drawn into, this will be set to transparent in production.

Resources