192 lines
6.2 KiB
Swift
192 lines
6.2 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
public import SignalServiceKit
|
|
|
|
public extension UIImageView {
|
|
|
|
func setImage(imageName: String) {
|
|
guard let image = UIImage(named: imageName) else {
|
|
owsFailDebug("Couldn't load image: \(imageName)")
|
|
return
|
|
}
|
|
self.image = image
|
|
}
|
|
|
|
func setTemplateImage(_ templateImage: UIImage?, tintColor: UIColor) {
|
|
guard let templateImage else {
|
|
owsFailDebug("Missing image")
|
|
return
|
|
}
|
|
self.image = templateImage.withRenderingMode(.alwaysTemplate)
|
|
self.tintColor = tintColor
|
|
}
|
|
|
|
func setTemplateImageName(_ imageName: String, tintColor: UIColor) {
|
|
guard let image = UIImage(named: imageName) else {
|
|
owsFailDebug("Couldn't load image: \(imageName)")
|
|
return
|
|
}
|
|
setTemplateImage(image, tintColor: tintColor)
|
|
}
|
|
|
|
class func withTemplateImage(_ templateImage: UIImage?, tintColor: UIColor) -> UIImageView {
|
|
let imageView = UIImageView()
|
|
imageView.setTemplateImage(templateImage, tintColor: tintColor)
|
|
return imageView
|
|
}
|
|
|
|
class func withTemplateImageName(_ imageName: String, tintColor: UIColor) -> UIImageView {
|
|
let imageView = UIImageView()
|
|
imageView.setTemplateImageName(imageName, tintColor: tintColor)
|
|
return imageView
|
|
}
|
|
|
|
/// Creates an image view with the given theme icon, tinted with the given
|
|
/// color, and constrained to the given size if present.
|
|
/// - Parameters:
|
|
/// - icon: The ``ThemeIcon`` to display.
|
|
/// - tintColor: The color to tint the icon
|
|
/// - size: The size to constrain the image to.
|
|
/// When `nil`, no constraints are added.
|
|
/// - Returns: A `UIImageView` of the icon.
|
|
class func withTemplateIcon(
|
|
_ icon: ThemeIcon,
|
|
tintColor: UIColor,
|
|
constrainedTo size: CGSize? = nil,
|
|
) -> UIImageView {
|
|
let imageView = UIImageView()
|
|
imageView.setTemplateImage(Theme.iconImage(icon), tintColor: tintColor)
|
|
if let size {
|
|
imageView.autoSetDimensions(to: size)
|
|
}
|
|
return imageView
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public extension UIImage {
|
|
/// Redraw the image into a new image, with an added background color, and inset the
|
|
/// original image by the provided insets.
|
|
func withBackgroundColor(_ color: UIColor, insets: UIEdgeInsets = .zero) -> UIImage? {
|
|
let bounds = CGRect(origin: .zero, size: size)
|
|
return UIGraphicsImageRenderer(bounds: bounds).image { context in
|
|
color.setFill()
|
|
context.fill(bounds)
|
|
draw(in: bounds.inset(by: insets))
|
|
}
|
|
}
|
|
|
|
func normalized() -> UIImage {
|
|
guard imageOrientation != .up else {
|
|
return self
|
|
}
|
|
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.scale = scale
|
|
format.opaque = false
|
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
|
return renderer.image { context in
|
|
self.draw(in: CGRect(origin: CGPoint.zero, size: size))
|
|
}
|
|
}
|
|
|
|
func withTitle(
|
|
_ title: String,
|
|
font: UIFont,
|
|
color: UIColor,
|
|
maxTitleWidth: CGFloat,
|
|
minimumScaleFactor: CGFloat,
|
|
spacing: CGFloat,
|
|
) -> UIImage? {
|
|
let titleLabel = UILabel()
|
|
titleLabel.text = title
|
|
titleLabel.font = font
|
|
titleLabel.adjustsFontSizeToFitWidth = true
|
|
titleLabel.minimumScaleFactor = minimumScaleFactor
|
|
titleLabel.textAlignment = .center
|
|
titleLabel.textColor = color
|
|
titleLabel.numberOfLines = title.components(separatedBy: " ").count > 1 ? 2 : 1
|
|
titleLabel.lineBreakMode = .byTruncatingTail
|
|
|
|
let titleSize = titleLabel.textRect(forBounds: CGRect(
|
|
origin: .zero,
|
|
size: CGSize(
|
|
width: maxTitleWidth,
|
|
height: .greatestFiniteMagnitude,
|
|
),
|
|
), limitedToNumberOfLines: titleLabel.numberOfLines).size
|
|
let additionalWidth = size.width >= titleSize.width ? 0 : titleSize.width - size.width
|
|
|
|
var newSize = size
|
|
newSize.height += spacing + titleSize.height
|
|
newSize.width = max(titleSize.width, size.width)
|
|
|
|
UIGraphicsBeginImageContextWithOptions(newSize, false, max(scale, UIScreen.main.scale))
|
|
|
|
// Draw the image into the new image
|
|
draw(in: CGRect(origin: CGPoint(x: additionalWidth / 2, y: 0), size: size))
|
|
|
|
// Draw the title label into the new image
|
|
titleLabel.drawText(in: CGRect(origin: CGPoint(
|
|
x: size.width > titleSize.width ? (size.width - titleSize.width) / 2 : 0,
|
|
y: size.height + spacing,
|
|
), size: titleSize))
|
|
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|
|
|
UIGraphicsEndImageContext()
|
|
|
|
return newImage
|
|
}
|
|
|
|
func withBadge(
|
|
color: UIColor,
|
|
badgeSize: CGSize = .square(8.5),
|
|
) -> UIImage {
|
|
let newSize = CGSize(
|
|
width: size.width + (badgeSize.width / 2.0),
|
|
height: size.height + (badgeSize.height / 2.0),
|
|
)
|
|
|
|
let renderer = UIGraphicsImageRenderer(size: newSize)
|
|
return renderer.image { context in
|
|
// Draw the image
|
|
draw(at: .zero)
|
|
|
|
// Draw the badge in the top-right corner, over the image
|
|
let badgeOrigin = CGPoint(x: size.width - badgeSize.width, y: 0)
|
|
let badgeRect = CGRect(origin: badgeOrigin, size: badgeSize)
|
|
let badgePath = UIBezierPath(ovalIn: badgeRect)
|
|
color.setFill()
|
|
badgePath.fill()
|
|
}
|
|
.withRenderingMode(.alwaysOriginal)
|
|
}
|
|
}
|
|
|
|
// MARK: -
|
|
|
|
public extension UIView {
|
|
|
|
func renderAsImage() -> UIImage {
|
|
renderAsImage(opaque: false, scale: UIScreen.main.scale)
|
|
}
|
|
|
|
func renderAsImage(opaque: Bool, scale: CGFloat) -> UIImage {
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.scale = scale
|
|
format.opaque = opaque
|
|
let renderer = UIGraphicsImageRenderer(
|
|
bounds: self.bounds,
|
|
format: format,
|
|
)
|
|
return renderer.image { context in
|
|
self.layer.render(in: context.cgContext)
|
|
}
|
|
}
|
|
}
|