Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make image sizes configurable #213

Merged
merged 8 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ opt_in_rules:

line_length: 140
file_length: 1000
function_body_length: 60
function_body_length: 40
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ internal class ConfigurationExampleViewController: UIViewController {
actionButton.buttonImage = #imageLiteral(resourceName: "Dots")
actionButton.buttonColor = .red
actionButton.buttonImageColor = .white
actionButton.buttonImageSize = CGSize(width: 30, height: 30)

actionButton.buttonAnimationConfiguration = .transition(toImage: #imageLiteral(resourceName: "X"))
actionButton.itemAnimationConfiguration = .slideIn(withInterItemSpacing: 14)
Expand All @@ -76,6 +77,10 @@ internal class ConfigurationExampleViewController: UIViewController {
item.layer.shadowRadius = CGFloat(2)
}

addItems()
}

fileprivate func addItems() {
actionButton.addItem(title: "Balloon", image: #imageLiteral(resourceName: "Baloon")) { item in
Helper.showAlert(for: item)
}
Expand All @@ -98,6 +103,7 @@ internal class ConfigurationExampleViewController: UIViewController {
heartItem.imageView.image = #imageLiteral(resourceName: "Favourite")
heartItem.buttonColor = .clear
heartItem.buttonImageColor = .red
heartItem.imageSize = CGSize(width: 30, height: 30)
heartItem.action = { item in
Helper.showAlert(for: item)
}
Expand Down
10 changes: 10 additions & 0 deletions Example/Tests/JJActionItemSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ class JJActionItemSpec: QuickSpec {
actionItem.titleLabel.text = ""
expect(actionItem) == snapshot()
}

it("looks correct with smaller image size") {
actionItem.imageSize = CGSize(width: 10, height: 10)
expect(actionItem) == snapshot()
}

it("looks correct with bigger image size") {
actionItem.imageSize = CGSize(width: 30, height: 30)
expect(actionItem) == snapshot()
}
}

describe("JJActionItem loaded from xib") {
Expand Down
10 changes: 10 additions & 0 deletions Example/Tests/JJFloatingActionButtonSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,16 @@ class JJFloatingActionButtonSpec: QuickSpec {
expect(superview) == snapshot()
}

it("looks correct with smaller image size") {
actionButton.buttonImageSize = CGSize(width: 10, height: 10)
expect(superview) == snapshot()
}

it("looks correct with bigger image size") {
actionButton.buttonImageSize = CGSize(width: 40, height: 40)
expect(superview) == snapshot()
}

context("when multiple items are added") {
var action = "not done"
beforeEach {
Expand Down
Binary file added ...ages/JJActionItemSpec/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ages/JJActionItemSpec/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...enceImages/JJActionItemSpec/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ges/JJActionItemSpec/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ...ges/JJActionItemSpec/[email protected]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
89 changes: 59 additions & 30 deletions Sources/JJActionItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ import UIKit
}

/// The position of the title label. Default is `-1`.
/// When titleSpacing is negative default spacing is used:
/// When `titleSpacing` is negative default spacing is used:
/// Default horizontal spacing is `12`.
/// Default vertical spaicng is `4`.
///
Expand All @@ -156,6 +156,17 @@ import UIKit
}
}

/// The size of the image view. Default is `(0, 0)`.
/// When imageSize is `.zero` the image is shrunken until it fits
/// compleately into the circle view. If it already does the actual
/// size of the image is used.
///
@objc public dynamic var imageSize: CGSize = .zero {
didSet {
setNeedsUpdateConstraints()
}
}

internal override init(frame: CGRect) {
super.init(frame: frame)
setup()
Expand Down Expand Up @@ -247,11 +258,8 @@ fileprivate extension JJActionItem {
var constraints: [NSLayoutConstraint] = []
var constraint: NSLayoutConstraint

let imageSizeMuliplier = CGFloat(1 / sqrt(2))
constraints.append(imageView.centerXAnchor.constraint(equalTo: circleView.centerXAnchor))
constraints.append(imageView.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
constraints.append(imageView.widthAnchor.constraint(lessThanOrEqualTo: circleView.widthAnchor, multiplier: imageSizeMuliplier))
constraints.append(imageView.heightAnchor.constraint(lessThanOrEqualTo: circleView.heightAnchor, multiplier: imageSizeMuliplier))

constraints.append(circleView.widthAnchor.constraint(equalTo: circleView.heightAnchor))

Expand Down Expand Up @@ -287,44 +295,65 @@ fileprivate extension JJActionItem {
}

func createDynamicConstraints() {
dynamicConstraints.append(contentsOf: titleSpacingConstraints)
dynamicConstraints.append(contentsOf: imageSizeConstraints)
}

var titleSpacingConstraints: [NSLayoutConstraint] {
var constraints: [NSLayoutConstraint] = []
let horizontalSpacing = titleSpacing(forAxis: .horizontal)
let verticalSpacing = titleSpacing(forAxis: .vertical)

switch titlePosition {
case .leading:
dynamicConstraints.append(circleView.trailingAnchor.constraint(equalTo: trailingAnchor))
dynamicConstraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
dynamicConstraints.append(titleLabel.trailingAnchor.constraint(equalTo: circleView.leadingAnchor, constant: -horizontalSpacing))
dynamicConstraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
constraints.append(circleView.trailingAnchor.constraint(equalTo: trailingAnchor))
constraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
constraints.append(titleLabel.trailingAnchor.constraint(equalTo: circleView.leadingAnchor, constant: -horizontalSpacing))
constraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
case .trailing:
dynamicConstraints.append(circleView.leadingAnchor.constraint(equalTo: leadingAnchor))
dynamicConstraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
dynamicConstraints.append(titleLabel.leadingAnchor.constraint(equalTo: circleView.trailingAnchor, constant: horizontalSpacing))
dynamicConstraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
constraints.append(circleView.leadingAnchor.constraint(equalTo: leadingAnchor))
constraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
constraints.append(titleLabel.leadingAnchor.constraint(equalTo: circleView.trailingAnchor, constant: horizontalSpacing))
constraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
case .left:
dynamicConstraints.append(circleView.rightAnchor.constraint(equalTo: rightAnchor))
dynamicConstraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
dynamicConstraints.append(titleLabel.rightAnchor.constraint(equalTo: circleView.leftAnchor, constant: -horizontalSpacing))
dynamicConstraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
constraints.append(circleView.rightAnchor.constraint(equalTo: rightAnchor))
constraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
constraints.append(titleLabel.rightAnchor.constraint(equalTo: circleView.leftAnchor, constant: -horizontalSpacing))
constraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
case .right:
dynamicConstraints.append(circleView.leftAnchor.constraint(equalTo: leftAnchor))
dynamicConstraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
dynamicConstraints.append(titleLabel.leftAnchor.constraint(equalTo: circleView.rightAnchor, constant: horizontalSpacing))
dynamicConstraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
constraints.append(circleView.leftAnchor.constraint(equalTo: leftAnchor))
constraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
constraints.append(titleLabel.leftAnchor.constraint(equalTo: circleView.rightAnchor, constant: horizontalSpacing))
constraints.append(titleLabel.centerYAnchor.constraint(equalTo: circleView.centerYAnchor))
case .top:
dynamicConstraints.append(circleView.bottomAnchor.constraint(equalTo: bottomAnchor))
dynamicConstraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
dynamicConstraints.append(titleLabel.bottomAnchor.constraint(equalTo: circleView.topAnchor, constant: -verticalSpacing))
dynamicConstraints.append(titleLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor))
constraints.append(circleView.bottomAnchor.constraint(equalTo: bottomAnchor))
constraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
constraints.append(titleLabel.bottomAnchor.constraint(equalTo: circleView.topAnchor, constant: -verticalSpacing))
constraints.append(titleLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor))
case .bottom:
dynamicConstraints.append(circleView.topAnchor.constraint(equalTo: topAnchor))
dynamicConstraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
dynamicConstraints.append(titleLabel.topAnchor.constraint(equalTo: circleView.bottomAnchor, constant: verticalSpacing))
dynamicConstraints.append(titleLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor))
constraints.append(circleView.topAnchor.constraint(equalTo: topAnchor))
constraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
constraints.append(titleLabel.topAnchor.constraint(equalTo: circleView.bottomAnchor, constant: verticalSpacing))
constraints.append(titleLabel.centerXAnchor.constraint(equalTo: circleView.centerXAnchor))
case .hidden:
dynamicConstraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
dynamicConstraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
constraints.append(circleView.centerXAnchor.constraint(equalTo: centerXAnchor))
constraints.append(circleView.centerYAnchor.constraint(equalTo: centerYAnchor))
}

return constraints
}

var imageSizeConstraints: [NSLayoutConstraint] {
var constraints: [NSLayoutConstraint] = []
if imageSize == .zero {
let muliplier = CGFloat(1 / sqrt(2))
constraints.append(imageView.widthAnchor.constraint(lessThanOrEqualTo: circleView.widthAnchor, multiplier: muliplier))
constraints.append(imageView.heightAnchor.constraint(lessThanOrEqualTo: circleView.heightAnchor, multiplier: muliplier))
} else {
constraints.append(imageView.widthAnchor.constraint(equalToConstant: imageSize.width))
constraints.append(imageView.heightAnchor.constraint(equalToConstant: imageSize.height))
}
return constraints
}

func titleSpacing(forAxis axis: NSLayoutConstraint.Axis) -> CGFloat {
Expand Down
60 changes: 55 additions & 5 deletions Sources/JJFloatingActionButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ import UIKit
}
}

/// The size of the image view.
/// Default is `CGSize.zero`.
/// If set to `.zero` the actual size of the image is used.
///
/// - SeeAlso: `imageView`
///
@objc public dynamic var buttonImageSize: CGSize = .zero {
didSet {
setNeedsUpdateConstraints()
}
}

/// The tint color of the image view.
/// Default is `UIColor.white`.
///
Expand Down Expand Up @@ -269,6 +281,8 @@ import UIKit

fileprivate var defaultItemConfiguration: ((JJActionItem) -> Void)?
fileprivate var itemsWithSetup: Set<JJActionItem> = []

fileprivate var dynamicConstraints: [NSLayoutConstraint] = []
}

// MARK: - Public Methods
Expand Down Expand Up @@ -369,6 +383,13 @@ extension JJFloatingActionButton {
open override var intrinsicContentSize: CGSize {
return CGSize(width: buttonDiameter, height: buttonDiameter)
}

/// Updates constraints for the view.
///
open override func updateConstraints() {
updateDynamicConstraints()
super.updateConstraints()
}
}

// MARK: - Setup
Expand All @@ -389,6 +410,15 @@ fileprivate extension JJFloatingActionButton {
circleView.addSubview(imageView)

circleView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false

createStaticConstraints()
createDynamicConstraints()

configureButtonImage()
}

func createStaticConstraints() {
circleView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
circleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
circleView.widthAnchor.constraint(equalTo: circleView.heightAnchor).isActive = true
Expand All @@ -401,19 +431,14 @@ fileprivate extension JJFloatingActionButton {
heightConstraint.priority = .defaultHigh
heightConstraint.isActive = true

let imageSizeMuliplier = CGFloat(1 / sqrt(2))
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.centerXAnchor.constraint(equalTo: circleView.centerXAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: circleView.centerYAnchor).isActive = true
imageView.widthAnchor.constraint(lessThanOrEqualTo: circleView.widthAnchor, multiplier: imageSizeMuliplier).isActive = true
imageView.heightAnchor.constraint(lessThanOrEqualTo: circleView.heightAnchor, multiplier: imageSizeMuliplier).isActive = true

imageView.setContentCompressionResistancePriority(.fittingSizeLevel, for: .horizontal)
imageView.setContentCompressionResistancePriority(.fittingSizeLevel, for: .vertical)
circleView.setContentHuggingPriority(.fittingSizeLevel, for: .horizontal)
circleView.setContentHuggingPriority(.fittingSizeLevel, for: .vertical)

configureButtonImage()
}

func configureButtonImage() {
Expand All @@ -437,6 +462,31 @@ fileprivate extension JJFloatingActionButton {

defaultItemConfiguration?(item)
}

func updateDynamicConstraints() {
NSLayoutConstraint.deactivate(dynamicConstraints)
dynamicConstraints.removeAll()
createDynamicConstraints()
NSLayoutConstraint.activate(dynamicConstraints)
setNeedsLayout()
}

func createDynamicConstraints() {
dynamicConstraints.append(contentsOf: imageSizeConstraints)
}

var imageSizeConstraints: [NSLayoutConstraint] {
var constraints: [NSLayoutConstraint] = []
if buttonImageSize == .zero {
let muliplier = CGFloat(1 / sqrt(2))
constraints.append(imageView.widthAnchor.constraint(lessThanOrEqualTo: circleView.widthAnchor, multiplier: muliplier))
constraints.append(imageView.heightAnchor.constraint(lessThanOrEqualTo: circleView.heightAnchor, multiplier: muliplier))
} else {
constraints.append(imageView.widthAnchor.constraint(equalToConstant: buttonImageSize.width))
constraints.append(imageView.heightAnchor.constraint(equalToConstant: buttonImageSize.height))
}
return constraints
}
}

// MARK: - Helper
Expand Down