iOS - add image and text to titleview with constraints - ios

I am trying to make a custom title view for my nav bar with an image an some text next to it and this is how I did it thank to https://stackoverflow.com/a/47404105 :
class CustomTitleView: UIView
{
var title_label = CustomLabel()
var left_imageView = UIImageView()
override init(frame: CGRect){
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
setup()
}
func setup(){
self.addSubview(title_label)
self.addSubview(left_imageView)
}
func loadWith(title: String, leftImage: UIImage?)
{
title_label.text = title
title_label.font = UIFont.systemFont(ofSize: FontManager.fontSize + 5)
left_imageView.image = leftImage
setupFrames()
}
func setupFrames()
{
let height: CGFloat = 44
let image_size: CGFloat = height * 0.8
left_imageView.frame = CGRect(x: 0,
y: (height - image_size) / 2,
width: (left_imageView.image == nil) ? 0 : image_size,
height: image_size)
let titleWidth: CGFloat = title_label.intrinsicContentSize.width + 10
title_label.frame = CGRect(x: left_imageView.frame.maxX + 5,
y: 0,
width: titleWidth,
height: height)
contentWidth = Int(left_imageView.frame.width)
self.frame = CGRect(x: 0, y: 0, width: CGFloat(contentWidth), height: height)
}
var contentWidth: Int = 0
override func layoutSubviews() {
super.layoutSubviews()
self.frame.size.width = CGFloat(contentWidth)
}
But the problem is that I don't want to hardcode the frame, I want to use constraints but I have no idea where to start.

First take a imageView and set it's constraints according to your requirement and then take a Label besides that imageView set it's constraints too. Do this in your navigationBar where you want to show them.

Related

Creating a haptic-touch-like button in Swift/Obj-C

I've been playing around with an idea for a button which, when held, expands to reveal other buttons (like the FAB in Android). Without releasing, sliding one's finger down should highlight other buttons, similar to haptic touch's behaviour.
Here's a crude mockup I've created using Drama: https://youtu.be/Iam8Gjv3gqM.
There should also be an option to have a label horizontally next to each button, and the order of buttons should be as shown (with the selected button at the top instead of its usual position).
I already have a class for each button (seen below), but do not know how to achieve this layout/behaviour.
class iDUButton: UIButton {
init(image: UIImage? = nil) {
super.init(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
if let image = image {
setImage(image, for: .normal)
}
backgroundColor = .secondarySystemFill
layer.cornerRadius = 15
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
}
class iDUBadgeButton: iDUButton {
var badgeLabel = UILabel()
var badgeCount = 0
override init(image: UIImage? = nil) {
super.init(image: image)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
private func setup() {
badgeLabel.textColor = .white
badgeLabel.backgroundColor = .systemRed
badgeLabel.textAlignment = .center
badgeLabel.font = .preferredFont(forTextStyle: .caption1)
badgeLabel.alpha = 0
updateBadge()
addSubview(badgeLabel)
}
override func layoutSubviews() {
super.layoutSubviews()
badgeLabel.sizeToFit()
let height = max(18, badgeLabel.frame.height + 5.0)
let width = max(height, badgeLabel.frame.width + 10.0)
badgeLabel.frame = CGRect(x: frame.width - 5, y: -badgeLabel.frame.height / 2, width: width, height: height);
badgeLabel.layer.cornerRadius = badgeLabel.frame.height / 2;
badgeLabel.layer.masksToBounds = true;
}
func setBadgeCount(badgeCount: Int) {
self.badgeCount = badgeCount;
self.updateBadge()
}
func updateBadge() {
if badgeCount != 0 {
badgeLabel.text = "\(badgeCount)"
}
UIView.animate(withDuration: 0.25, animations: {
self.badgeLabel.alpha = self.badgeCount == 0 ? 0 : 1
})
layoutSubviews()
}
}
If anyone could help with achieving this layout, I'd be very grateful! Thanks in advance.
PS: I'm developing the UI in Swift, but will be converting it to Objective-C once complete. If you're interested, the use case is to adapt the button in the top-left of this to offer a selection of different tags.

Imitate SVProgressHUD behavior

I`ve implemented a UIView that display a CustomLottie in the center of the screen, it has show() and hide() methods.
How can I give it the ability of hide() it from another place than where show() was called??
This is the code:
class LottieProgressHUD: UIView {
static let shared = LottieProgressHUD()
let hudView: UIView
var animationView: AnimationView
//options
var hudWidth:CGFloat = 200
var hudHeight:CGFloat = 200
var animationFileName = "coinLoading"
override init(frame: CGRect) {
self.hudView = UIView()
self.animationView = AnimationView()
super.init(frame: frame)
self.setup()
}
required init?(coder aDecoder: NSCoder) {
self.hudView = UIView()
self.animationView = AnimationView()
super.init(coder: aDecoder)
self.setup()
}
func setup() {
self.addSubview(hudView)
show()
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
if let superview = self.superview {
self.animationView.removeFromSuperview()
let screenRect = UIScreen.main.bounds
let screenWidth = screenRect.size.width
let screenHeight = screenRect.size.height
let width: CGFloat = self.hudWidth
let height: CGFloat = self.hudHeight
self.frame = CGRect(x: (screenWidth / 2) - (width / 2) ,y: (screenHeight / 2) - (height / 2), width: width, height: height)
hudView.frame = self.bounds
layer.masksToBounds = true
self.animationView = AnimationView(name: self.animationFileName)
self.animationView.frame = CGRect(x: 0, y: 0, width: self.hudView.frame.width, height: self.hudView.frame.size.height)
self.animationView.contentMode = .scaleAspectFill
self.hudView.addSubview(animationView)
self.animationView.loopMode = .loop
self.animationView.play()
self.hide()
}
}
func show() {
self.isHidden = false
}
func hide() {
self.isHidden = true
}
}
This is how I call it inside the VC:
let progressHUD = LottieProgressHUD.shared
self.view.addSubview(progressHUD)
progressHUD.show()
I would like to call progressHUD.hide() inside the VM
You already have a static property that you can reference the same instance from anywhere. Should be able to call your show and hide methods from any class.
LottieProgressHUD.shared.show()
LottieProgressHUD.shared.hide()

UIButton align image left and center text

Introduction:
I have a class, which inherits from UIButton. In this class I want to update properties, like titleEdgeInsets, imageEdgeInsets, contentHorizontalAlignment.
My first approach was to use layoutSubviews:
override func layoutSubviews() {
super.layoutSubviews()
// update properties
}
The layoutSubviews creates an infinity loop, so that I've searched for an alternative method.
My Question:
Is it a common way, to use the willMove method for updating UIButton properties?
override func willMove(toWindow newWindow: UIWindow?) {
super.willMove(toWindow: newWindow)
// update properties
}
If not, why?
My goal is to align the imageView of the button left (with padding) and center the text.
UPDATE:
I need the button frame.size and the bounds.width to calculate the position of the text and the image view
All the properties you mentioned above can be set in the init of the UIButton there is absolutely no need to set them in layoutSubviews or willMove(toWindow.
layoutSubviews will be called multiple times so setting these properties again n agin in here makes no sense. willMove(toWindow will be called when button is added to some view and button is loaded but you dont have to wait till then to set these properties. Because you already have a subclass of button, so I would suggest doing
class SomeButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.contentHorizontalAlignment = .center
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
By the way creating a subclass of UIButton is not recommended, so if you wanna simply assign these properties to your button you can rather have a extension to UIButton
extension UIButton {
func applyStyle() {
self.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
self.contentHorizontalAlignment = .center
}
}
EDIT:
Is this what you want??
No matter what the text is, text is always in centre and image is to its left with 10 pixel padding
EDIT 2:
As OP has confirmed that, he wants the button to be styled as showed in images above, posting the code to achieve the same
class SomeButton: UIButton {
var titleFont: UIFont! = nil
var textSize: CGFloat = 0
let imageWidth: CGFloat = 20
let buttonHeight: CGFloat = 30
override init(frame: CGRect) {
super.init(frame: frame)
self.titleFont = titleLabel!.font
self.setTitle("here", for: .normal)
self.setTitleColor(UIColor.red, for: .normal)
self.setImage(UIImage(named: "hand"), for: .normal)
self.backgroundColor = UIColor.green
}
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
if let string = self.title(for: .normal) {
textSize = string.widthOfString(usingFont: self.titleFont)
//30 because imageWidth + 10 padding
return CGRect(origin: CGPoint(x: 30, y: 0), size: CGSize(width: textSize + 30, height: buttonHeight))
}
return CGRect.zero
}
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
return CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: imageWidth, height: buttonHeight))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override var intrinsicContentSize: CGSize {
//60 because you need eauql padding on both side 30 + 30 = 60
return CGSize(width: textSize + 60, height: buttonHeight)
}
}
extension String {
func widthOfString(usingFont font: UIFont) -> CGFloat {
let fontAttributes = [NSAttributedString.Key.font: font]
let size = self.size(withAttributes: fontAttributes)
return size.width
}
}
Hope it helps

Background Colour in UIView not showing

I have a UIView that should show two colours, red and orange, based on the value of rating: Double The problem is that when I run the app nothing is showing up. In my output log the function prints that it has run and that the rating value is what it should be. So I am not sure why nothing is showing up when I run the app, I just see white.
class RatingViewController: UIView {
var rating: Double = 1.0
var rate: Double? {
didSet {
rating = rate!
setUpView()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func setUpView() {
Self.backgroundcolor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
You have to add setUpView in init(: method
class RatingViewController: UIView {
var rating: Double = 1.0
required init?(coder: NSCoder) {
super.init(coder: coder)
}
init(frame: CGRect, rate: Double) {
super.init(frame: frame)
self.rating = rate
setUpView()
}
private func setUpView() {
self.backgroundColor = UIColor.yellow
print("rating is \(rating), and width is \((UIScreen.main.bounds.width * CGFloat(rating/10)))")
let width = (UIScreen.main.bounds.width * CGFloat(rating/10))
var view: UIView
view = UIView(frame: CGRect(x: 0, y: 0, width: width, height: self.frame.size.height))
view.backgroundColor = UIColor.red
self.addSubview(view)
}
}
Now you can call this class with custom init method like that:
// chnage frame and rate according to your requirment
let rView = RatingViewController(frame: CGRect(x: 0, y: 0, width: 320, height: 300), rate: 2.0)

Collection Cell Content Size

I added a view as parent red view in CollectionViewCell and the next blue subview at the center of the parent view. It works correctly and the sub view goes at the center of the parent view before collection cell size is not changed. But, The cell size is changed by conforming the method from UICollectionViewDelegateFlowLayout protocol and the view is not centered of the cell correctly. How I can solve this issue ?
class ItemCollectionViewCell: UICollectionViewCell {
var parentView: UIView!
var circularView: UIView!
var itemImage: UIImageView!
var itemName: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
// self.updateView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.updateView()
}
func updateView(){
self.clipsToBounds = true
self.parentView = UIView(frame: CGRect(x: 0.0, y: 0.0, width:
self.frame.size.width, height: self.frame.size.height))
self.parentView.backgroundColor = UIColor.red
self.circularView = UIView(frame: CGRect(x: 0, y: 0, width:
self.parentView.frame.size.width / 4 , height:
self.parentView.frame.size.width / 4 ))
self.circularView.backgroundColor = UIColor.blue
self.addSubview(parentView)
self.parentView.addSubview(self.circularView)
self.circularView.center = self.parentView.center
}
}
1]2
Try this code
self.circularView.center = CGPoint(x: self. parentView.bounds.midX, y: self. parentView.bounds.midY)
self.parentView.addSubview(self.circularView)
self.addSubview(parentView)
Try doing:
self.circularView.center = CGPointMake(CGRectGetMidX(self.parentView.bounds), CGRectGetMidY(self.parentView.bounds))
Why this should work? Try checking values of self.parentView.center for each cell, they might not be what you want them to be, because center property gives values with respect to parent view coordinate system.
Remove the following line from updateView()
self.circularView.center = self.parentView.center
Please add it to the following method:
override func layoutSubviews() {
super.layoutSubviews()
self.circularView.center = self.parentView.center
}
e.g.
class ItemCollectionViewCell: UICollectionViewCell {
var parentView: UIView!
var circularView: UIView!
var itemImage: UIImageView!
var itemName: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
// self.updateView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.updateView()
}
func updateView(){
self.clipsToBounds = true
self.parentView = UIView(frame: CGRect(x: 0.0, y: 0.0, width:
self.frame.size.width, height: self.frame.size.height))
self.parentView.backgroundColor = UIColor.red
self.circularView = UIView(frame: CGRect(x: 0, y: 0, width:
self.parentView.frame.size.width / 4 , height:
self.parentView.frame.size.width / 4 ))
self.circularView.backgroundColor = UIColor.blue
self.addSubview(parentView)
self.parentView.addSubview(self.circularView)
}
override func layoutSubviews() {
super.layoutSubviews()
self.circularView.center = self.parentView.center
}
}

Resources