mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			182 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			182 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import SessionUtilitiesKit
 | |
| 
 | |
| public extension UIContextualAction {
 | |
|     private static var lookupMap: Atomic<[Int: [String: [Int: ThemeValue]]]> = Atomic([:])
 | |
|     
 | |
|     enum Side: Int {
 | |
|         case leading
 | |
|         case trailing
 | |
|         
 | |
|         func key(for indexPath: IndexPath) -> String {
 | |
|             return "\(indexPath.section)-\(indexPath.row)-\(rawValue)"
 | |
|         }
 | |
|         
 | |
|         init?(for view: UIView) {
 | |
|             guard view.frame.minX == 0 else {
 | |
|                 self = .trailing
 | |
|                 return
 | |
|             }
 | |
|             
 | |
|             self = .leading
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     convenience init(
 | |
|         title: String? = nil,
 | |
|         icon: UIImage? = nil,
 | |
|         iconHeight: CGFloat = Values.mediumFontSize,
 | |
|         themeTintColor: ThemeValue = .white,
 | |
|         themeBackgroundColor: ThemeValue,
 | |
|         side: Side,
 | |
|         actionIndex: Int,
 | |
|         indexPath: IndexPath,
 | |
|         tableView: UITableView,
 | |
|         handler: @escaping UIContextualAction.Handler
 | |
|     ) {
 | |
|         self.init(style: .normal, title: title, handler: handler)
 | |
|         self.image = UIContextualAction
 | |
|             .imageWith(
 | |
|                 title: title,
 | |
|                 icon: icon,
 | |
|                 iconHeight: iconHeight,
 | |
|                 themeTintColor: themeTintColor
 | |
|             )?
 | |
|             .withRenderingMode(.alwaysTemplate)
 | |
|         self.themeBackgroundColor = themeBackgroundColor
 | |
|         
 | |
|         UIContextualAction.lookupMap.mutate {
 | |
|             $0[tableView.hashValue] = ($0[tableView.hashValue] ?? [:])
 | |
|                 .setting(
 | |
|                     side.key(for: indexPath),
 | |
|                     (($0[tableView.hashValue] ?? [:])[side.key(for: indexPath)] ?? [:])
 | |
|                         .setting(actionIndex, themeTintColor)
 | |
|                 )
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private static func imageWith(
 | |
|         title: String?,
 | |
|         icon: UIImage?,
 | |
|         iconHeight: CGFloat,
 | |
|         themeTintColor: ThemeValue
 | |
|     ) -> UIImage? {
 | |
|         let stackView: UIStackView = UIStackView()
 | |
|         stackView.axis = .vertical
 | |
|         stackView.alignment = .center
 | |
|         stackView.spacing = 4
 | |
|         
 | |
|         if let icon: UIImage = icon {
 | |
|             let aspectRatio: CGFloat = (icon.size.width / icon.size.height)
 | |
|             let imageView: UIImageView = UIImageView(image: icon)
 | |
|             imageView.frame = CGRect(x: 0, y: 0, width: (iconHeight * aspectRatio), height: iconHeight)
 | |
|             imageView.contentMode = .scaleAspectFit
 | |
|             imageView.themeTintColor = themeTintColor
 | |
|             stackView.addArrangedSubview(imageView)
 | |
|         }
 | |
|         
 | |
|         if let title: String = title {
 | |
|             let label: UILabel = UILabel()
 | |
|             label.font = .systemFont(ofSize: Values.smallFontSize)
 | |
|             label.text = title
 | |
|             label.textAlignment = .center
 | |
|             label.themeTextColor = themeTintColor
 | |
|             label.minimumScaleFactor = 0.75
 | |
|             label.numberOfLines = (title.components(separatedBy: " ").count > 1 ? 2 : 1)
 | |
|             label.frame = CGRect(
 | |
|                 origin: .zero,
 | |
|                 // Note: It looks like there is a semi-max width of 68px for images in the swipe actions
 | |
|                 // if the image ends up larger then there an odd behaviour can occur where 8/10 times the
 | |
|                 // image is scaled down to fit, but ocassionally (primarily if you hide the action and
 | |
|                 // immediately swipe to show it again once the cell hits the edge of the screen) the image
 | |
|                 // won't be scaled down but will be full size - appearing as if two different images are used
 | |
|                 size: label.sizeThatFits(CGSize(width: 68, height: 999))
 | |
|             )
 | |
|             label.set(.width, to: label.frame.width)
 | |
|             
 | |
|             stackView.addArrangedSubview(label)
 | |
|         }
 | |
|         
 | |
|         stackView.frame = CGRect(
 | |
|             origin: .zero,
 | |
|             size: stackView.systemLayoutSizeFitting(CGSize(width: 999, height: 999))
 | |
|         )
 | |
|         
 | |
|         // Based on https://stackoverflow.com/a/41288197/1118398
 | |
|         let renderFormat: UIGraphicsImageRendererFormat = UIGraphicsImageRendererFormat()
 | |
|         renderFormat.scale = UIScreen.main.scale
 | |
|         
 | |
|         let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(
 | |
|             size: stackView.bounds.size,
 | |
|             format: renderFormat
 | |
|         )
 | |
|         return renderer.image { rendererContext in
 | |
|             stackView.layer.render(in: rendererContext.cgContext)
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private static func firstSubviewOfType<T>(in superview: UIView) -> T? {
 | |
|         guard !(superview is T) else { return superview as? T }
 | |
|         guard !superview.subviews.isEmpty else { return nil }
 | |
|         
 | |
|         for subview in superview.subviews {
 | |
|             if let result: T = firstSubviewOfType(in: subview) {
 | |
|                 return result
 | |
|             }
 | |
|         }
 | |
|         
 | |
|         return nil
 | |
|     }
 | |
|     
 | |
|     static func willBeginEditing(indexPath: IndexPath, tableView: UITableView) {
 | |
|         guard
 | |
|             let targetCell: UITableViewCell = tableView.cellForRow(at: indexPath),
 | |
|             targetCell.superview != tableView,
 | |
|             let targetSuperview: UIView = targetCell.superview?
 | |
|                 .subviews
 | |
|                 .filter({ $0 != targetCell })
 | |
|                 .first,
 | |
|             let side: Side = Side(for: targetSuperview),
 | |
|             let themeMap: [Int: ThemeValue] = UIContextualAction.lookupMap.wrappedValue
 | |
|                 .getting(tableView.hashValue)?
 | |
|                 .getting(side.key(for: indexPath)),
 | |
|             targetSuperview.subviews.count == themeMap.count
 | |
|         else { return }
 | |
|         
 | |
|         let targetViews: [UIImageView] = targetSuperview.subviews
 | |
|             .compactMap { subview in firstSubviewOfType(in: subview) }
 | |
|         
 | |
|         guard targetViews.count == themeMap.count else { return }
 | |
|         
 | |
|         // Set the imageView and background colours (so they change correctly when the theme changes)
 | |
|         targetViews.enumerated().forEach { index, targetView in
 | |
|             guard let themeTintColor: ThemeValue = themeMap[index] else { return }
 | |
|             
 | |
|             targetView.themeTintColor = themeTintColor
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     static func didEndEditing(indexPath: IndexPath?, tableView: UITableView) {
 | |
|         guard let indexPath: IndexPath = indexPath else { return }
 | |
|         
 | |
|         let leadingKey: String = Side.leading.key(for: indexPath)
 | |
|         let trailingKey: String = Side.trailing.key(for: indexPath)
 | |
|         
 | |
|         guard
 | |
|             UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[leadingKey] != nil ||
 | |
|             UIContextualAction.lookupMap.wrappedValue[tableView.hashValue]?[trailingKey] != nil
 | |
|         else { return }
 | |
|         
 | |
|         UIContextualAction.lookupMap.mutate {
 | |
|             $0[tableView.hashValue]?[leadingKey] = nil
 | |
|             $0[tableView.hashValue]?[trailingKey] = nil
 | |
|             
 | |
|             if $0[tableView.hashValue]?.isEmpty == true {
 | |
|                 $0[tableView.hashValue] = nil
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 |