Skip to content

Instantly share code, notes, and snippets.

@albertbori
Created November 21, 2017 21:49
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save albertbori/23b2c2ee1b24f3ba3a3dfbe852b82dc1 to your computer and use it in GitHub Desktop.
Save albertbori/23b2c2ee1b24f3ba3a3dfbe852b82dc1 to your computer and use it in GitHub Desktop.
//
// StandardTooltip.swift
//
// Created by Albert Bori on 11/20/17.
//
import Foundation
@objc
class StandardTooltip: NSObject {
var style: StandardTooltipStyle
var positioning: StandardTooltipPositioning
var text: String?
var attributedText: NSAttributedString?
var contentView: UIView?
var onDismiss: (()->())?
var forcePositioning: Bool = true //TODO: Figure out how to dynamically position tooltip based on display logic
var minXMargin: CGFloat = 10
var minYMargin: CGFloat = 10
var padding: UIEdgeInsets = UIEdgeInsets(top: 13, left: 20, bottom: 13, right: 20)
var cornerRadius: CGFloat?
var canTapTooltipToDismiss: Bool = true
var canTapBackgroundToDismiss: Bool = true
private var backgroundView: UIView!
private var carotView: UIView!
private var tooltipView: UIView!
private static var tooltipStore: [StandardTooltip] = []
init(style: StandardTooltipStyle = StandardTooltipStyle.default, positioning: StandardTooltipPositioning = .automatic, onDismiss: (()->())? = nil) {
self.style = style
self.positioning = positioning
if let onDismiss = onDismiss {
self.onDismiss = onDismiss
}
}
convenience init(text: String, style: StandardTooltipStyle = StandardTooltipStyle.default, positioning: StandardTooltipPositioning = .automatic, onDismiss: (()->())? = nil) {
self.init(style: style, positioning: positioning, onDismiss: onDismiss)
self.text = text
}
convenience init(attributedText: NSAttributedString, style: StandardTooltipStyle = StandardTooltipStyle.default, positioning: StandardTooltipPositioning = .automatic, onDismiss: (()->())? = nil) {
self.init(style: style, positioning: positioning, onDismiss: onDismiss)
self.attributedText = attributedText
}
convenience init(contentView: UIView, style: StandardTooltipStyle = StandardTooltipStyle.default, positioning: StandardTooltipPositioning = .automatic, onDismiss: (()->())? = nil) {
self.init(style: style, positioning: positioning, onDismiss: onDismiss)
self.contentView = contentView
}
func show(inView parentView: UIView, anchoredTo anchorView: UIView) {
backgroundView = UIView(frame: parentView.frame)
parentView.add(subview: backgroundView, topMargin: 0, leftMargin: 0, bottomMargin: 0, rightMargin: 0)
if canTapBackgroundToDismiss {
let tap = UITapGestureRecognizer(target: self, action: #selector(hide))
backgroundView.addGestureRecognizer(tap)
}
let innerView: UIView
if let contentView = contentView {
innerView = contentView
} else {
let label = UILabel()
label.font = UIFont(name: "HelveticaNeue-Bold", size: 13)
label.numberOfLines = 0
label.textAlignment = .center
if let text = text {
label.text = text
label.textColor = UIColor.white
}
if let attributedText = attributedText {
label.attributedText = attributedText
}
innerView = label
}
innerView.translatesAutoresizingMaskIntoConstraints = false
tooltipView = UIView()
if let cornerRadius = cornerRadius {
tooltipView.cornerRadius = cornerRadius
}
tooltipView.backgroundColor = UIColor.RGBColor(51, green: 51, blue: 51)
tooltipView.add(subview: innerView, topMargin: padding.top, leftMargin: padding.left, bottomMargin: padding.bottom, rightMargin: padding.right)
let actualPosition = forcePositioning && positioning != .automatic ? positioning : getPositioningForSize(parentView: parentView, anchorView: anchorView, contentView: tooltipView)
let anchorIsInLeftHalfOfWindow: Bool
if let window = parentView.window, let anchorFrame = anchorView.superview?.convert(anchorView.frame, to: window) {
anchorIsInLeftHalfOfWindow = anchorFrame.centerX <= window.frame.width / 2
} else {
anchorIsInLeftHalfOfWindow = false
}
carotView = UIImageView(image: UIImage(named: actualPosition == .below ? "tooltipTriangleUp" : "arrowBottomBlack"))
parentView.add(subview: carotView)
carotView.widthAnchor.constraint(equalToConstant: 13).isActive = true
carotView.centerXAnchor.constraint(equalTo: anchorView.centerXAnchor).isActive = true
if actualPosition == .above {
carotView.bottomAnchor.constraint(equalTo: anchorView.topAnchor, constant: 6).isActive = true
} else {
anchorView.bottomAnchor.constraint(equalTo: carotView.topAnchor, constant: 6).isActive = true
}
parentView.add(subview: tooltipView)
tooltipView.leadingAnchor.constraint(greaterThanOrEqualTo: parentView.leadingAnchor, constant: minXMargin).isActive = true
parentView.trailingAnchor.constraint(greaterThanOrEqualTo: tooltipView.trailingAnchor, constant: minXMargin).isActive = true
if actualPosition == .above {
tooltipView.bottomAnchor.constraint(equalTo: carotView.topAnchor).isActive = true
} else {
carotView.bottomAnchor.constraint(equalTo: tooltipView.topAnchor).isActive = true
}
if anchorIsInLeftHalfOfWindow {
carotView.leadingAnchor.constraint(equalTo: tooltipView.leadingAnchor, constant: 36).isActive = true
} else {
tooltipView.trailingAnchor.constraint(equalTo: carotView.trailingAnchor, constant: 36).isActive = true
}
if canTapTooltipToDismiss {
let tap = UITapGestureRecognizer(target: self, action: #selector(hide))
tooltipView.addGestureRecognizer(tap)
}
if cornerRadius == nil { //default is auto-corner radius
tooltipView.setNeedsLayout()
tooltipView.layoutIfNeeded()
tooltipView.cornerRadius = tooltipView.frame.height / 2
}
StandardTooltip.tooltipStore.append(self)
}
func hide() {
backgroundView.removeFromSuperview()
tooltipView.removeFromSuperview()
carotView.removeFromSuperview()
backgroundView = nil
tooltipView = nil
carotView = nil
StandardTooltip.tooltipStore.remove({ $0 === self })
}
private func getPositioningForSize(parentView: UIView, anchorView: UIView, contentView: UIView) -> StandardTooltipPositioning {
guard let anchorFrame = anchorView.superview?.convert(anchorView.frame, to: parentView) else {
return StandardTooltipPositioning.below
}
var parentSize = parentView.frame.size
parentSize.width -= minXMargin*2
parentSize.height -= minYMargin*2
let fittingSize = contentView.sizeThatFits(parentSize)
let fitsAbove = fittingSize.height + (minXMargin*2) <= anchorFrame.origin.y
let fitsBelow = fittingSize.height + (minYMargin*2) <= parentView.frame.size.height - anchorFrame.origin.y - anchorFrame.size.height
if positioning == .automatic {
var windowPositionPreference: StandardTooltipPositioning = .below
if let window = parentView.window, let anchorFrame = anchorView.superview?.convert(anchorView.frame, to: window) {
let anchorViewIsTopHalfOfWindow = anchorFrame.centerY <= window.frame.height / 2
windowPositionPreference = anchorViewIsTopHalfOfWindow ? .below : .above
}
if fitsAbove && !(fitsBelow && windowPositionPreference == .below) {
return .above
} else {
return .below
}
}
if fitsAbove && !(fitsBelow && positioning == .below) {
return .above
} else {
return .below
}
}
}
enum StandardTooltipStylez {
case `default`
}
enum StandardTooltipPositioningz {
case automatic,
above,
below
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment