Instantly share code, notes, and snippets.
Last active
January 2, 2025 08:35
-
Star
(7)
7
You must be signed in to star a gist -
Fork
(1)
1
You must be signed in to fork a gist
-
Save lifeutilityapps/dcb8137ea097a94034ddeeb25253fc2e to your computer and use it in GitHub Desktop.
A reusable SwiftUI context menu component for programmatically creating a menu of user-copiable values
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// StandardCopyMenu.swift | |
// DownPay for iOS | |
// lifeutilityapps.com/downpay | |
// | |
// Created by Life Utility Apps on 12/15/24. | |
// | |
import SwiftUI | |
enum EStandardCopyMenuItem { | |
case value | |
case label | |
case divider | |
case date | |
} | |
struct StandardCopyMenuItem: Identifiable { | |
var label: String = "" | |
var toCopy: String = "" | |
var type: EStandardCopyMenuItem = .value | |
var onCopy: (() -> Void)? = nil | |
var isLabeled: Bool = false | |
let id = UUID() | |
} | |
extension Array where Element == StandardCopyMenuItem { | |
mutating func addDivider() { | |
self.append(.init(type: .divider)) | |
} | |
mutating func addLabel(_ label: String) { | |
self.append(.init(label: label, type: .label)) | |
} | |
} | |
// Scroll below for how to use this | |
struct StandardCopyMenu: View { | |
var labelMenu: String = "Copy" | |
var systemImage: String = SCL.icons.copy.iconFill() | |
var items: [StandardCopyMenuItem] = [] | |
func handleCopy(_ item: StandardCopyMenuItem) { | |
SCL.util.copyText(item.toCopy) | |
SCL.util.vibrateDevice(.rigid) | |
// Optional callback | |
if let onCopy = item.onCopy { | |
onCopy() | |
} | |
} | |
var body: some View { | |
Menu(labelMenu, systemImage: systemImage) { | |
ForEach(items) { item in | |
if(item.type == .divider) { | |
Divider() | |
} else if(item.type == .date) { | |
let dateItems = StandardCopyMenu.getDateVariations(item.toCopy) | |
ForEach(dateItems) { itemDate in | |
Button { | |
handleCopy(itemDate) | |
} label: { | |
Label(itemDate.toCopy, systemImage: SCL.icons.copy) | |
} | |
} | |
} else if(item.type == .label) { | |
Text(item.label) | |
} else { | |
if(item.isLabeled) { | |
Text(item.label) | |
} | |
Button { | |
handleCopy(item) | |
} label: { | |
Label(item.toCopy, systemImage: SCL.icons.copy) | |
} | |
} | |
} | |
} | |
} | |
static func getDateVariations(_ dateISO: String, _ onCopy: (() -> Void)? = nil) -> [StandardCopyMenuItem] { | |
var result:[StandardCopyMenuItem] = [] | |
let dateTypes: [EStandardDateTextVariant] = [.Mdyyyy, .MMMMdyyyy, .fullDate] | |
let date = Date(fromISOStringJS: dateISO) | |
dateTypes.forEach { dateType in | |
let dateString = StandardDateText.getDateString(dateType, date) | |
result.append(.init(label: "", toCopy: dateString, onCopy: onCopy)) | |
} | |
return result | |
} | |
} | |
// Example usage creating the copiable values in a model | |
extension ModelUserTransaction { | |
var copyItems: [StandardCopyMenuItem] { | |
var result: [StandardCopyMenuItem] = [] | |
result.append(.init(label: "Amount", toCopy: SCL.cash(self.amount, false, .small, true), isLabeled: true)) | |
result.append(.init(label: "Amount Number", toCopy: String(self.amount))) | |
// Divider | |
result.append(.init(type: .divider)) | |
let hasNotes = self.notes.isNotEmpty | |
if(self.label.isNotEmpty) { | |
let label = "Label\(hasNotes ? " & Notes" : "")" | |
result.append(.init(label: label, toCopy: self.label, isLabeled: true)) | |
} | |
if(hasNotes) { | |
result.append(.init(label: "Notes", toCopy: self.notes)) | |
} | |
// Divider | |
result.addDivider() | |
// Manually created label | |
result.addLabel("Date") | |
// Date (will automatically create variations) | |
result.append(.init(label: "Date", toCopy: self.dateTransactionOccured.ISO8601Format(), type: .date)) | |
// Another closing divider | |
result.addDivider() | |
if(self.transactionType == .payment) { | |
result.append(.init(label: "Category", toCopy: self.wrappedPaymentCategory.displayName, isLabeled: true)) | |
} | |
return result | |
} | |
} | |
// Example usage in any view | |
/* | |
SomeView() | |
.contextMenu { | |
StandardCopyMenu(items: transaction.copyItems) | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment