Animate a circle from the center in Swift - ios

I have a circle that I'm animating. It works except that the drawing is from the top left.. Can I animate it from the center? If so, any help would be appreciated..
My code for drawing the circle is:
class CircleView: UIView {
override func draw(_ rect: CGRect)
{
let prefs: UserDefaults = UserDefaults.standard
lineWidthFloat = prefs.value(forKey: "lineWidth") as! Float
let circleSize = Double(lineWidthFloat * 100)
let context = UIGraphicsGetCurrentContext()
context!.setLineWidth(10.0)
context!.setFillColor(UIColor.black.cgColor)
let rect = CGRect(x: 20, y: 20, width: circleSize, height: circleSize)
context!.addEllipse(inRect: rect)
context!.fillPath()
}
}
Thanks!

Details
Xcode 10.2.1 (10E1001), Swift 5
Full Sample
CircleView
class CircleView: UIView {
weak var circleView: UIView?
lazy var isAnimating = false
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
private func setup() {
let rectSide = (frame.size.width > frame.size.height) ? frame.size.height : frame.size.width
let circleRect = CGRect(x: (frame.size.width-rectSide)/2, y: (frame.size.height-rectSide)/2, width: rectSide, height: rectSide)
let circleView = UIView(frame: circleRect)
circleView.backgroundColor = UIColor.yellow
circleView.layer.cornerRadius = rectSide/2
circleView.layer.borderWidth = 2.0
circleView.layer.borderColor = UIColor.red.cgColor
addSubview(circleView)
self.circleView = circleView
}
func resizeCircle (summand: CGFloat) {
guard let circleView = circleView else { return }
frame.origin.x -= summand/2
frame.origin.y -= summand/2
frame.size.height += summand
frame.size.width += summand
circleView.frame.size.height += summand
circleView.frame.size.width += summand
}
private func animateChangingCornerRadius (toValue: Any?, duration: TimeInterval) {
guard let circleView = circleView else { return }
let animation = CABasicAnimation(keyPath:"cornerRadius")
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.fromValue = circleView.layer.cornerRadius
animation.toValue = toValue
animation.duration = duration
circleView.layer.cornerRadius = circleView.frame.size.width/2
circleView.layer.add(animation, forKey:"cornerRadius")
}
private func circlePulseAinmation(_ summand: CGFloat, duration: TimeInterval, completionBlock:#escaping ()->()) {
guard let circleView = circleView else { return }
UIView.animate(withDuration: duration, delay: 0, options: .curveEaseInOut, animations: { [weak self] in
self?.resizeCircle(summand: summand)
}) { _ in completionBlock() }
animateChangingCornerRadius(toValue: circleView.frame.size.width/2, duration: duration)
}
func resizeCircleWithPulseAinmation(_ summand: CGFloat, duration: TimeInterval) {
if (!isAnimating) {
isAnimating = true
circlePulseAinmation(summand, duration:duration) { [weak self] in
guard let self = self else { return }
self.circlePulseAinmation((-1)*summand, duration:duration) {self.isAnimating = false}
}
}
}
}
ViewController
import UIKit
class ViewController: UIViewController {
weak var circleView: CircleView?
weak var button: UIButton?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let circleView = CircleView(frame: CGRect(x: 40, y: 50, width: 40, height: 60))
circleView.backgroundColor = UIColor.clear
view.addSubview(circleView)
self.circleView = circleView
let button = UIButton(frame: CGRect(x: 20, y: 150, width: 80, height: 40))
button.setTitle("Animate", for: UIControl.State())
button.setTitleColor(UIColor.blue, for: UIControl.State())
button.setTitleColor(UIColor.blue.withAlphaComponent(0.3), for: .highlighted)
button.addTarget(self, action: #selector(ViewController.animateCircle), for: .touchUpInside)
view.addSubview(button)
self.button = button
}
#objc func animateCircle() {
circleView?.resizeCircleWithPulseAinmation(30, duration: 1.5)
}
}
Result

You should be able to do it a lot simpler using the transform property.
func pulse() {
UIView.animate(withDuration: 0.5, animations:{
self.circleView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}, completion: { _ in
UIView.animate(withDuration: 0.5, animations: {
self.circleView.transform = .identity
})
})
}

Related

Draggable connected UIViews in Swift

I am trying to create some draggable UIViews that are connected by lines. See image below:
I can create the draggable circles by creating a class that is a subclass of UIView and overriding the draw function
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: rect)
let circleColor:UIColor
switch group {
case .forehead:
circleColor = UIColor.red
case .crowsFeetRightEye:
circleColor = UIColor.green
case .crowsFeetLeftEye:
circleColor = UIColor.blue
}
circleColor.setFill()
path.fill()
}
and then add a pan gesture recognizer for the dragging
func initGestureRecognizers() {
let panGR = UIPanGestureRecognizer(target: self, action: #selector(DragPoint.didPan(panGR:)))
addGestureRecognizer(panGR)
}
#objc func didPan(panGR: UIPanGestureRecognizer) {
if panGR.state == .changed {
self.superview!.bringSubview(toFront: self)
let translation = panGR.translation(in: self)
self.center.x += translation.x
self.center.y += translation.y
panGR.setTranslation(CGPoint.zero, in: self)
}
}
However, I am totally stuck on how to go about the connecting lines and parenting the start/end points to its corresponding circle when dragged. Is there anybody who can help or point me in the right direction please?
You want to use CAShapeLayers with UIBezier paths to draw the lines between the circles and then change the paths when the user moves the views.
Here is a playground showing an implementation. You can copy and paste this into a playground to see it in action.
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class CircleView : UIView {
var outGoingLine : CAShapeLayer?
var inComingLine : CAShapeLayer?
var inComingCircle : CircleView?
var outGoingCircle : CircleView?
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.cornerRadius = self.frame.size.width / 2
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func lineTo(circle: CircleView) -> CAShapeLayer {
let path = UIBezierPath()
path.move(to: self.center)
path.addLine(to: circle.center)
let line = CAShapeLayer()
line.path = path.cgPath
line.lineWidth = 5
line.strokeColor = UIColor.red.cgColor
circle.inComingLine = line
outGoingLine = line
outGoingCircle = circle
circle.inComingCircle = self
return line
}
}
class MyViewController : UIViewController {
let circle1 = CircleView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
let circle2 = CircleView(frame: CGRect(x: 100, y: 200, width: 50, height: 50))
let circle3 = CircleView(frame: CGRect(x: 100, y: 300, width: 50, height: 50))
let circle4 = CircleView(frame: CGRect(x: 100, y: 400, width: 50, height: 50))
override func loadView() {
let view = UIView()
view.backgroundColor = .white
self.view = view
circle1.backgroundColor = .red
view.addSubview(circle1)
circle2.backgroundColor = .red
view.addSubview(circle2)
circle3.backgroundColor = .red
view.addSubview(circle3)
circle4.backgroundColor = .red
view.addSubview(circle4)
circle1.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle2.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle3.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
circle4.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(didPan(gesture:))))
view.layer.addSublayer(circle1.lineTo(circle: circle2))
view.layer.addSublayer(circle2.lineTo(circle: circle3))
view.layer.addSublayer(circle3.lineTo(circle: circle4))
}
#objc func didPan(gesture: UIPanGestureRecognizer) {
guard let circle = gesture.view as? CircleView else {
return
}
if (gesture.state == .began) {
circle.center = gesture.location(in: self.view)
}
let newCenter: CGPoint = gesture.location(in: self.view)
let dX = newCenter.x - circle.center.x
let dY = newCenter.y - circle.center.y
circle.center = CGPoint(x: circle.center.x + dX, y: circle.center.y + dY)
if let outGoingCircle = circle.outGoingCircle, let line = circle.outGoingLine, let path = circle.outGoingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: circle.center)
newPath.addLine(to: outGoingCircle.center)
line.path = newPath.cgPath
}
if let inComingCircle = circle.inComingCircle, let line = circle.inComingLine, let path = circle.inComingLine?.path {
let newPath = UIBezierPath(cgPath: path)
newPath.removeAllPoints()
newPath.move(to: inComingCircle.center)
newPath.addLine(to: circle.center)
line.path = newPath.cgPath
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
PlaygroundPage.current.needsIndefiniteExecution = true

Custom view doesn't appear for the first time swift

When user open my app for the first time, my custom view doesn't appear. But the next time it works fine.
I am calling this view when user taps on button.
let menu = MenuView(image: image, title: "text", buttons: buttons)
menu.show(animated: true)
Custom View code
class MenuView: UIView, Menu {
var background = UIView()
var blackOverlay = UIView()
convenience init(image: UIImage, title: String, buttons: [UIButton]) {
self.init(frame: UIScreen.main.bounds)
setupView(image: image, title: title, buttons: buttons)
}
#objc func cancelTapped() {
hide(animated: true)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupView(image: UIImage, title: String, buttons: [UIButton]) {
blackOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
blackOverlay.frame = CGRect(x: 0, y: 0, width: frame.width, height: frame.height)
addSubview(blackOverlay)
let backgroundWidth = self.frame.width - CGFloat(80.0)
let imageView = UIImageView(frame: CGRect(x: (backgroundWidth/2)-17, y: 40, width: 34, height: 34))
imageView.image = image
imageView.contentMode = .center
background.addSubview(imageView)
let titleLabel = UILabel()
let stringHeight = title.stringHeight + 14
titleLabel.frame = CGRect(x: background.center.x+8, y: 100, width: backgroundWidth-16, height: stringHeight)
titleLabel.font = UIFont.systemFont(ofSize: 15, weight: .regular)
titleLabel.text = title
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 0
background.addSubview(titleLabel)
var newHeight: CGFloat = 0
for i in 0...buttons.count-1 {
buttons[i].frame.origin = CGPoint(x: 0, y: titleLabel.frame.height + 125 + CGFloat(i*50))
buttons[i].frame.size = CGSize(width: backgroundWidth, height: 50)
buttons[i].setTitleColor(.gingerColor, for: .normal)
buttons[i].setTitleColor(UIColor.gingerColor.withAlphaComponent(0.5), for: .highlighted)
buttons[i].titleLabel?.textAlignment = .center
newHeight+=buttons[i].frame.height
let separator = UIView()
separator.frame.origin = CGPoint(x: 0, y: titleLabel.frame.height + 125 + CGFloat(i*50))
separator.frame.size = CGSize(width: frame.width, height: 1)
separator.backgroundColor = UIColor(hexString: "dedede")
background.addSubview(separator)
if i == buttons.count-1 {
buttons[i].setTitleColor(UIColor(hexString: "9E9E9E"), for: .normal)
}
buttons[i].addTarget(self, action: #selector(self.cancelTapped), for: .touchUpInside)
background.addSubview(buttons[i])
}
background.frame.origin = CGPoint(x: center.x, y: frame.height)
background.frame.size = CGSize(width: frame.width-80, height: 90 + titleLabel.frame.height+imageView.frame.height + CGFloat(newHeight))
background.backgroundColor = .white
background.layer.cornerRadius = 16
background.layer.masksToBounds = true
addSubview(background)
}
}
and my custom view protocol & extension code:
protocol Menu {
func show(animated: Bool)
func hide(animated: Bool)
var blackOverlay: UIView { get }
var background: UIView { get }
}
extension Menu where Self: UIView {
func show(animated: Bool) {
self.blackOverlay.alpha = 0
self.background.alpha = 0
self.blackOverlay.center = self.center
self.background.center = CGPoint(x: self.center.x, y: self.frame.height+self.background.frame.height/2)
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(self)
if animated {
UIView.animate(withDuration: 0.33, animations: {
self.blackOverlay.alpha = 1
self.background.alpha = 1
})
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: {
self.background.center = self.center
}, completion: { (completed) in
print("completed is \(completed)")
})
} else {
self.blackOverlay.alpha = 1
self.background.alpha = 1
self.background.center = self.center
}
}
func hide(animated: Bool) {
if animated {
UIView.animate(withDuration: 0.33, animations: {
self.blackOverlay.alpha = 0
self.background.alpha = 0
})
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: {
self.background.center = CGPoint(x: self.center.x, y: self.frame.height + self.background.frame.height/2)
}, completion: { (completed) in
self.removeFromSuperview()
})
} else {
self.removeFromSuperview()
}
}
}
As you can see I have a parameter completed in completion animation block, it returns false for the first time. All subsequent times it returns false
I found my mistake.
I tried to present my custom view controller above rootController. But, for the very first time user's rootcontroller is the first screen of onboarding. So, my custom view tried to be shown on another controller.

UITableView delegate methods are not called when added on GooeySlideMenu

I want to add UITableView on top of UIView which is a subclass of GooeySlideMenu. As in example shown in git I created UITableView instead of UIButtons but delegate methods are not called.
Below is my code for reference:
class GooeySlideMenu: UIView {
fileprivate var _option: MenuOptions
fileprivate var keyWindow: UIWindow?
fileprivate var blurView: UIVisualEffectView!
fileprivate var helperSideView: UIView!
fileprivate var helperCenterView: UIView!
fileprivate var diff: CGFloat = 0.0
fileprivate var triggered: Bool = false
fileprivate var displayLink: CADisplayLink?
fileprivate var animationCount: Int = 0
fileprivate var myTableView: tableViewCustomClass = tableViewCustomClass()
init(options: MenuOptions) {
_option = options
if let kWindow = UIApplication.shared.keyWindow{
keyWindow = kWindow
let frame = CGRect(
x: -kWindow.frame.size.width/2 - options.menuBlankWidth,
y: 0,
width: kWindow.frame.size.width/2 + options.menuBlankWidth,
height: kWindow.frame.size.height)
super.init(frame:frame)
} else {
super.init(frame:CGRect.zero)
}
setUpViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: frame.width-_option.menuBlankWidth, y: 0))
path.addQuadCurve(to: CGPoint(x: frame.width-_option.menuBlankWidth, y: frame.height), controlPoint: CGPoint(x: frame.width-_option.menuBlankWidth+diff, y: frame.height/2))
path.addLine(to: CGPoint(x: 0, y: frame.height))
path.close()
let context = UIGraphicsGetCurrentContext()
context?.addPath(path.cgPath)
_option.menuColor.set()
context?.fillPath()
}
func trigger() {
if !triggered {
if let keyWindow = keyWindow {
keyWindow.insertSubview(blurView, belowSubview: self)
UIView.animate(withDuration: 0.3, animations: { [weak self] () -> Void in
self?.frame = CGRect(
x: 0,
y: 0,
width: keyWindow.frame.size.width/2 + (self?._option.menuBlankWidth)!,
height: keyWindow.frame.size.height)
})
beforeAnimation()
UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.9, options: [.beginFromCurrentState,.allowUserInteraction], animations: { [weak self] () -> Void in
self?.helperSideView.center = CGPoint(x: keyWindow.center.x, y: (self?.helperSideView.frame.size.height)!/2);
}, completion: { [weak self] (finish) -> Void in
self?.finishAnimation()
})
UIView.animate(withDuration: 0.3, animations: { [weak self] () -> Void in
self?.blurView.alpha = 1.0
})
beforeAnimation()
UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 2.0, options: [.beginFromCurrentState,.allowUserInteraction], animations: { [weak self] () -> Void in
self?.helperCenterView.center = keyWindow.center
}, completion: { [weak self] (finished) -> Void in
if finished {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(GooeySlideMenu.tapToUntrigger))
self?.blurView.addGestureRecognizer(tapGesture)
self?.finishAnimation()
}
})
// animateButtons()
myTableView.reloadData()
triggered = true
}
} else {
tapToUntrigger()
}
}
}
extension GooeySlideMenu {
fileprivate func setUpViews() {
if let keyWindow = keyWindow {
blurView = UIVisualEffectView(effect: UIBlurEffect(style: _option.blurStyle))
blurView.frame = keyWindow.frame
blurView.alpha = 0.0
helperSideView = UIView(frame: CGRect(x: -40, y: 0, width: 40, height: 40))
helperSideView.backgroundColor = UIColor.red
helperSideView.isHidden = true
keyWindow.addSubview(helperSideView)
helperCenterView = UIView(frame: CGRect(x: -40, y: keyWindow.frame.height/2 - 20, width: 40, height: 40))
helperCenterView.backgroundColor = UIColor.yellow
helperCenterView.isHidden = true
keyWindow.addSubview(helperCenterView)
backgroundColor = UIColor.clear
keyWindow.insertSubview(self, belowSubview: helperSideView)
addUItableView()
// addButton()
}
}
fileprivate func addUItableView(){
myTableView.frame = CGRect(x: 0, y: 20, width: 300, height: 200)
myTableView.backgroundColor = UIColor.white
myTableView.delegate = tableViewCustomClass() as? UITableViewDelegate
myTableView.dataSource = tableViewCustomClass() as? UITableViewDataSource
addSubview(myTableView)
}
You need to declared tableview's delegate on tableViewCustomClass's class instead of GooeySlideMenu class .
In tableViewCustomClass
self.delegate = self in tableViewCustomClass's initialiser method

Swift - Not seeing my custom view class called from another custom view class

Just trying to follow some youtube tutorials and learn about custom views. I am sure I am missing something very basic here. After reading here and the net for hours I am giving up.
I have a custom view that slides out. I am planning on filling it with different custom views as needed. The slideOutView displays fine. However, I am not able to see the other custom view called FlashCardView inside it. I am calling FlashCardView from SlideOutView.
Thanks for the help!
code for FlashCardView class:
import UIKit
protocol FlashCardViewDelegate: class {
}
class FlashCardView: UIView {
weak var delegate: FlashCardViewDelegate?
let questionLabel = UILabel()
let scrollView = UIScrollView()
let dummyView = UIView()
//MARK: Initialization
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.black
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
code for SlideOutView class:
import UIKit
class SlideOutView: UIView {
let slideOutView = UIView()
let menuBarLine = UIView()
let menuBarView = UIView()
let menuButton = UIButton()
let backButton = UIButton()
let flashCardView = FlashCardView()
let ratio = DeviceSpecificValues()
override init(frame: CGRect) {
super.init(frame: frame)
self.addView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func addView() {
if let window = UIApplication.shared.keyWindow {
slideOutView.frame = CGRect(x: 0, y: window.bounds.height, width: window.bounds.width, height: window.bounds.height / 2)
print("slideoutview frame is: \(slideOutView.frame)")
slideOutView.backgroundColor = UIColor.white
window.addSubview(slideOutView)
menuBarView.frame = CGRect(x: 0, y: 0, width: slideOutView.bounds.width, height: window.bounds.height / ratio.menuBarRatio)
menuBarView.backgroundColor = GlobalValues.menuBarViewColor
slideOutView.addSubview(menuBarView)
menuBarLine.frame = CGRect(x: 0, y: 0, width: menuBarView.bounds.width, height: GlobalValues.menuBarLineWidth)
menuBarLine.backgroundColor = GlobalValues.menuTextColor
menuBarView.addSubview(menuBarLine)
backButton.frame = CGRect(x: 0, y: 0, width: menuBarView.bounds.width * 0.1, height: menuBarView.bounds.height * 0.9)
backButton.center.y = menuBarView.bounds.height / 2 + GlobalValues.menuBarLineWidth
backButton.setTitle("<Back", for: .normal)
backButton.titleLabel?.adjustsFontSizeToFitWidth = true
backButton.setTitleColor(GlobalValues.menuTextColor, for: UIControlState())
backButton.showsTouchWhenHighlighted = true
menuBarView.addSubview(backButton)
menuButton.frame = CGRect(x: menuBarView.bounds.width * 0.9, y: 0, width: menuBarView.bounds.width * 0.1, height: menuBarView.bounds.height * 0.9)
menuButton.center.y = menuBarView.bounds.height / 2 + GlobalValues.menuBarLineWidth
menuButton.setTitle("Menu", for: .normal)
menuButton.titleLabel?.adjustsFontSizeToFitWidth = true
menuButton.setTitleColor(GlobalValues.menuTextColor, for: UIControlState())
menuButton.showsTouchWhenHighlighted = true
menuBarView.addSubview(menuButton)
addFlashCardView()
}
}
func slideIn() {
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveLinear, animations: {
if let window = UIApplication.shared.keyWindow {
self.slideOutView.frame = CGRect(x: 0, y: window.bounds.height, width: window.bounds.width, height: window.bounds.height / 2)
}
}, completion: nil)
}
func slideOut() {
UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveLinear, animations: {
if let window = UIApplication.shared.keyWindow {
self.slideOutView.frame = CGRect(x: 0, y: window.bounds.height / 2, width: window.bounds.width, height: window.bounds.height / 2)
}
}, completion: nil)
}
func addFlashCardView() {
flashCardView.frame = slideOutView.frame
slideOutView.addSubview(flashCardView)
}
}

IBDesignable - Arrange subviews added through interface builder

I am currently messing around with IBDesignable Views, and I am curious if anyone has been able to solve this. I would like to have views added through the interface builder be automatically arranged using a custom layout algorithm within my subview. The view works great when I run the app, but in the interface builder, the views do not rearrange in real time.
I have tried debugging my UIView class, but it seems at all times when the interface builder is initializing the element, it thinks it has zero subviews. It seems the interface builder does not give you a chance to arrange these views after the fact. However, I'm wondering if maybe there is just something I'm missing. Is it possible to rearrange subviews added from the interface builder within an IBDesignable class, and have the views show up rearranged in the interface builder?
Try using the provided method for a custom view and IBDesignable if you are not already. You also might need to refresh your views in Xcode or have it automatically refresh views. Below is the function you may be missing. This is never called in a live app. It is only called in Xcode IB.
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
}
In this instance setUpView is laying out my subviews.
Here is an example that I made. https://github.com/agibson73/ICONButton
import UIKit
#IBDesignable class AGIconButton: UIControl {
private var iconImageView : UIImageView!
private var iconLabel : UILabel!
private var mainSpacer : UIView!
private var highlightView:UIView!
private var widthContraint : NSLayoutConstraint!
var padding : CGFloat = 5
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
addTarget(self, action: #selector(AGIconButton.userDidTouchDown), for: .touchDown)
addTarget(self, action: #selector(AGIconButton.userDidTouchUp), for: .touchUpInside)
addTarget(self, action: #selector(AGIconButton.userDidTouchUpOutside), for: .touchUpOutside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpView()
addTarget(self, action: #selector(AGIconButton.userDidTouchDown), for: .touchDown)
addTarget(self, action: #selector(AGIconButton.userDidTouchUp), for: .touchUpInside)
addTarget(self, action: #selector(AGIconButton.userDidTouchUpOutside), for: .touchUpOutside)
}
//only called at design time
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setUpView()
addTarget(self, action: #selector(AGIconButton.userDidTouchDown), for: .touchDown)
addTarget(self, action: #selector(AGIconButton.userDidTouchUp), for: .touchUpInside)
addTarget(self, action: #selector(AGIconButton.userDidTouchUpOutside), for: .touchUpOutside)
}
#IBInspectable var iconImage: UIImage = UIImage() {
didSet {
iconImageView.image = iconImage
}
}
#IBInspectable var imageSize: CGFloat = 40 {
didSet {
setUpView()
}
}
#IBInspectable var imagePadding: CGFloat = 10 {
didSet {
setUpView()
}
}
#IBInspectable var iconText: String = "Icon Button Time" {
didSet {
setUpView()
}
}
#IBInspectable var iconTextSize: CGFloat = 15 {
didSet {
setUpView()
}
}
#IBInspectable var iconTextColor: UIColor = UIColor.black {
didSet {
setUpView()
}
}
#IBInspectable var alignment: Int = 1 {
didSet {
setUpView()
}
}
override var intrinsicContentSize: CGSize {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: iconTextSize)
label.text = iconText
label.sizeToFit()
return CGSize(width: imageSize + label.frame.width + imagePadding + (padding * 2), height: CGFloat(max(label.frame.height, imageSize) + padding * 2))
}
#IBInspectable var highLightColor: UIColor = UIColor.lightGray {
didSet {
setUpView()
}
}
#IBInspectable var shouldBounce: Bool = true
#IBInspectable var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor
}
}
#IBInspectable var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
private func setUpView(){
if iconImageView == nil{
iconImageView = UIImageView(image: iconImage)
iconImageView.contentMode = .scaleAspectFit
iconImageView.isUserInteractionEnabled = false
self.addSubview(iconImageView)
}
if mainSpacer == nil{
mainSpacer = UIView(frame: CGRect(x: 0, y: 0, width: imagePadding, height: self.bounds.height))
mainSpacer.isUserInteractionEnabled = false
self.addSubview(mainSpacer)
}
if iconLabel == nil{
iconLabel = UILabel()
iconLabel.isUserInteractionEnabled = false
self.addSubview(iconLabel)
}
if highlightView == nil{
highlightView = UIView(frame: self.bounds)
highlightView.autoresizingMask = [.flexibleWidth,.flexibleHeight]
highlightView.alpha = 0
highlightView.isUserInteractionEnabled = false
self.addSubview(highlightView)
self.bringSubview(toFront: highlightView)
}
highlightView.backgroundColor = highLightColor
iconLabel.font = UIFont.systemFont(ofSize: iconTextSize)
iconLabel.text = iconText
iconLabel.textColor = iconTextColor
iconLabel.sizeToFit()
var usedWidth : CGFloat = self.intrinsicContentSize.width
if bounds.width < usedWidth{
usedWidth = bounds.width
}
let maxImageHeight = min(self.bounds.height - padding, imageSize)
//resize iconlabel if we have to
if maxImageHeight + imagePadding + iconLabel.bounds.width + padding * 2 > usedWidth{
iconLabel.frame = CGRect(x: 0, y: 0, width: self.bounds.width - iconImageView.bounds.width - imagePadding - padding * 2, height: iconLabel.bounds.height)
iconLabel.fitFontForSize(minFontSize: 1, maxFontSize: iconTextSize, accuracy: 1.0)
}
let maxWidth = (self.bounds.width - iconLabel.bounds.width - maxImageHeight - imagePadding) / 2
switch alignment {
case 0:
//intrinsic left
iconImageView.frame = CGRect(x:padding, y: self.bounds.midY - maxImageHeight/2,width:maxImageHeight, height: maxImageHeight)
mainSpacer.frame = CGRect(x: maxImageHeight + padding, y: 0, width: imagePadding, height: self.bounds.height)
iconLabel.frame = CGRect(x: maxImageHeight + imagePadding + padding, y: 0, width: iconLabel.frame.width, height: bounds.height)
break
case 1:
//intrinsic center
iconImageView.frame = CGRect(x: maxWidth, y: self.bounds.midY - maxImageHeight/2,width:maxImageHeight, height: maxImageHeight)
mainSpacer.frame = CGRect(x: maxWidth + maxImageHeight, y: 0, width: imagePadding, height: self.bounds.height)
iconLabel.frame = CGRect(x: maxWidth + maxImageHeight + imagePadding, y: 0, width: iconLabel.frame.width, height: self.bounds.height)
break
case 2:
//intrinsic icon right text aligned right
iconLabel.frame = CGRect(x: maxWidth, y: 0, width: iconLabel.frame.width, height: self.bounds.height)
iconLabel.textAlignment = .right
mainSpacer.frame = CGRect(x: iconLabel.frame.width + maxWidth, y: 0, width: imagePadding, height: self.bounds.height)
iconImageView.frame = CGRect(x: iconLabel.frame.width + imagePadding + maxWidth, y: self.bounds.midY - maxImageHeight/2,width:maxImageHeight, height: maxImageHeight)
break
case 3:
//intrinsic center invert icon
iconLabel.frame = CGRect(x:maxWidth, y: 0, width: iconLabel.frame.width, height: self.bounds.height)
mainSpacer.frame = CGRect(x: maxWidth + iconLabel.bounds.width, y: 0, width: imagePadding, height: self.bounds.height)
iconImageView.frame = CGRect(x: maxWidth + iconLabel.bounds.width + imagePadding, y: self.bounds.midY - maxImageHeight/2,width:maxImageHeight, height: maxImageHeight)
break
default:
//intrinsic center
iconImageView.frame = CGRect(x: maxWidth, y: self.bounds.midY - maxImageHeight/2,width:maxImageHeight, height: maxImageHeight)
mainSpacer.frame = CGRect(x: maxWidth + maxImageHeight, y: 0, width: imagePadding, height: self.bounds.height)
iconLabel.frame = CGRect(x: maxWidth + maxImageHeight + imagePadding, y: 0, width: iconLabel.frame.width, height: self.bounds.height)
}
}
//layout subviews
override func layoutSubviews() {
super.layoutSubviews()
setUpView()
}
//MARK: Touch Events
//TODO: run on timer to simulate a real press
func userDidTouchDown(){
if shouldBounce == true{
animateBouncyDown()
}else{
self.animateHighlightTo(alpha: 0.3)
}
}
func userDidTouchUp(){
if shouldBounce == true{
animateBouncyUp()
}else{
self.animateHighlightTo(alpha: 0)
}
}
func userDidTouchUpOutside(){
if shouldBounce == true{
animateBouncyUp()
}else{
self.animateHighlightTo(alpha: 0)
}
}
func animateHighlightTo(alpha:CGFloat){
UIView.animate(withDuration: 0.2, animations: { [weak self] in
self?.highlightView.alpha = alpha
})
}
func animateBouncyDown(){
self.transform = CGAffineTransform.identity
UIView.animate(withDuration: 0.15, animations: { [weak self] in
self?.transform = CGAffineTransform(scaleX: 0.85, y: 0.85)
})
}
func animateBouncyUp(){
UIView.animate(withDuration: 0.2, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.8, options: .curveEaseInOut, animations: {[weak self] in
if self != nil{
self?.transform = CGAffineTransform.identity
}
}, completion: nil)
}
}
extension UILabel {
func fitFontForSize( minFontSize : CGFloat = 1.0, maxFontSize : CGFloat = 300.0, accuracy : CGFloat = 1.0) {
var maxFontSize = maxFontSize
var minFontSize = minFontSize
assert(maxFontSize > minFontSize)
layoutIfNeeded() // Can be removed at your own discretion
let constrainedSize = bounds.size
while maxFontSize - minFontSize > accuracy {
let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
font = font.withSize(midFontSize)
sizeToFit()
let checkSize : CGSize = bounds.size
if checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
font = font.withSize(minFontSize)
sizeToFit()
layoutIfNeeded()
}
}
There doesn't seem to be any way to do this. If you drop a custom control into another xib and add subviews to the custom control in Interface Builder, those subviews appear at the same level as the custom control in the view hierarchy when you load the xib. It looks like custom controls can not act as containers in other xibs.

Resources