From 80a52fa4db53d7507e4adea84be2d28153cfcf57 Mon Sep 17 00:00:00 2001 From: Roberto Zunica Date: Mon, 16 Feb 2026 15:47:13 +0100 Subject: [PATCH 1/2] fix: replace deprecated class constraints and iOS 10.2 compatibility --- .gitignore | 3 ++ QVRWeekView.podspec | 4 +- QVRWeekView/Classes/Common/EventLayer.swift | 40 +++++++++++++++---- .../Classes/Common/FrameCalculator.swift | 2 +- QVRWeekView/Classes/Views/DayViewCell.swift | 2 +- QVRWeekView/Classes/Views/WeekView.swift | 2 +- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index a866f9f..04455c8 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ Carthage # `pod install` in .travis.yml # # Pods/ + +# Ruby +.ruby-version \ No newline at end of file diff --git a/QVRWeekView.podspec b/QVRWeekView.podspec index 72d4778..784434a 100644 --- a/QVRWeekView.podspec +++ b/QVRWeekView.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'QVRWeekView' -s.version = '0.15.0' +s.version = '0.15.1' s.summary = 'QVRWeekView is a simple calendar week view with support for horizontal, vertical scrolling and zooming.' s.swift_version = '5' @@ -25,7 +25,7 @@ s.license = { :type => 'MIT', :file => 'LICENSE' } s.author = { 'Reinert' => 'reilemx@gmail.com' } s.source = { :git => 'https://github.com/Quivr/iOS-Week-View.git', :tag => 'v' + s.version.to_s } -s.ios.deployment_target = '9.0' +s.ios.deployment_target = '12.0' s.source_files = 'QVRWeekView/Classes/**/*.swift' s.resources = 'QVRWeekView/Classes/Xibs/*.xib' diff --git a/QVRWeekView/Classes/Common/EventLayer.swift b/QVRWeekView/Classes/Common/EventLayer.swift index d16940f..0849a12 100644 --- a/QVRWeekView/Classes/Common/EventLayer.swift +++ b/QVRWeekView/Classes/Common/EventLayer.swift @@ -185,21 +185,45 @@ class EventLayer: CALayer { if cleaned.isEmpty { return false } - - // Check if all characters are emoji or zero-width joiners + + // Check if all scalars are emoji or joiners/variation selectors. for scalar in cleaned.unicodeScalars { - // Allow emoji and variation selectors/joiners - if scalar.properties.isEmoji || scalar == "\u{200D}" { // Zero-width joiner - continue - } - // If we encounter a non-emoji character, it's not emoji-only - if !scalar.isASCII { + if isEmojiScalar(scalar) || isEmojiJoinerOrVariation(scalar) { continue } return false } return true } + + private func isEmojiScalar(_ scalar: UnicodeScalar) -> Bool { + if #available(iOS 10.2, *) { + return scalar.properties.isEmoji + } + + // Fallback ranges for iOS < 10.2. + switch scalar.value { + case 0x1F600...0x1F64F, // Emoticons + 0x1F300...0x1F5FF, // Misc Symbols and Pictographs + 0x1F680...0x1F6FF, // Transport and Map + 0x1F1E6...0x1F1FF, // Regional indicator symbols + 0x2600...0x26FF, // Misc symbols + 0x2700...0x27BF, // Dingbats + 0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs + 0x1F700...0x1F77F, // Alchemical Symbols + 0x1F780...0x1F7FF, // Geometric Shapes Extended + 0x1F800...0x1F8FF, // Supplemental Arrows-C + 0x1FA00...0x1FA6F, // Chess Symbols + 0x1FA70...0x1FAFF: // Symbols and Pictographs Extended-A + return true + default: + return false + } + } + + private func isEmojiJoinerOrVariation(_ scalar: UnicodeScalar) -> Bool { + return scalar == "\u{200D}" || (0xFE00...0xFE0F).contains(scalar.value) + } private func loadIconImage(named: String) -> UIImage? { // Try to load from main app bundle under tags namespace (Images.xcassets/tags/) diff --git a/QVRWeekView/Classes/Common/FrameCalculator.swift b/QVRWeekView/Classes/Common/FrameCalculator.swift index 67e40f6..4a13c1c 100644 --- a/QVRWeekView/Classes/Common/FrameCalculator.swift +++ b/QVRWeekView/Classes/Common/FrameCalculator.swift @@ -264,7 +264,7 @@ class FrameCalculator { // MARK: - FrameCalculator Delegate - // Protocol contains FrameCalculator delegate functions. -protocol FrameCalculatorDelegate: class { +protocol FrameCalculatorDelegate: AnyObject { // Delegate function passes solution back to main thread func passSolution(fromCalculator calculator: FrameCalculator, solution: [String: CGRect]?) } diff --git a/QVRWeekView/Classes/Views/DayViewCell.swift b/QVRWeekView/Classes/Views/DayViewCell.swift index a09c772..f0a37f5 100644 --- a/QVRWeekView/Classes/Views/DayViewCell.swift +++ b/QVRWeekView/Classes/Views/DayViewCell.swift @@ -404,7 +404,7 @@ class DayViewCell: UICollectionViewCell, CAAnimationDelegate { } -protocol DayViewCellDelegate: class { +protocol DayViewCellDelegate: AnyObject { func dayViewCellWasLongPressed(_ dayViewCell: DayViewCell, hours: Int, minutes: Int) diff --git a/QVRWeekView/Classes/Views/WeekView.swift b/QVRWeekView/Classes/Views/WeekView.swift index 7caefcf..42cfcb7 100644 --- a/QVRWeekView/Classes/Views/WeekView.swift +++ b/QVRWeekView/Classes/Views/WeekView.swift @@ -776,7 +776,7 @@ open class WeekView: UIView { // MARK: - WEEKVIEW DELEGATE - /// Protocol methods. -@objc public protocol WeekViewDelegate: class { +@objc public protocol WeekViewDelegate: AnyObject { func didLongPressDayView(in weekView: WeekView, atDate date: Date) func didTapEvent(in weekView: WeekView, withId eventId: String) From bc65cacc2bd42e38071580407938cfb85c0fef3e Mon Sep 17 00:00:00 2001 From: Roberto Zunica Date: Mon, 16 Feb 2026 16:36:19 +0100 Subject: [PATCH 2/2] feat: modernize Swift syntax, update iOS target, and enhance tag system documentation --- QVRWeekView.podspec | 2 +- QVRWeekView/Classes/Common/EventLayer.swift | 24 ++----- README.md | 73 +++++++++++++++++---- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/QVRWeekView.podspec b/QVRWeekView.podspec index 784434a..5c4841c 100644 --- a/QVRWeekView.podspec +++ b/QVRWeekView.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'QVRWeekView' -s.version = '0.15.1' +s.version = '0.15.2' s.summary = 'QVRWeekView is a simple calendar week view with support for horizontal, vertical scrolling and zooming.' s.swift_version = '5' diff --git a/QVRWeekView/Classes/Common/EventLayer.swift b/QVRWeekView/Classes/Common/EventLayer.swift index 0849a12..d3e2ed5 100644 --- a/QVRWeekView/Classes/Common/EventLayer.swift +++ b/QVRWeekView/Classes/Common/EventLayer.swift @@ -79,10 +79,8 @@ class EventLayer: CALayer { let tagName = eventTag.name let tagColor = eventTag.color - let tagLower = tagName.lowercased() - - // Try to load icon for any tag (automatically detects from Images.xcassets/tags/) - let iconImage = loadIconImage(named: tagLower) + // Try to load icon for the tag from app's Images.xcassets + let iconImage = loadIconImage(named: tagName) // Check if tag is emoji-only let isEmojiOnly = isEmoji(tagName) @@ -226,25 +224,13 @@ class EventLayer: CALayer { } private func loadIconImage(named: String) -> UIImage? { - // Try to load from main app bundle under tags namespace (Images.xcassets/tags/) + // Load tag image from app's main bundle (Images.xcassets) with "tags/" prefix (e.g., "tags/BED") if let image = UIImage(named: "tags/\(named)", in: Bundle.main, compatibleWith: nil) { return image } - // Try without namespace in main bundle - if let image = UIImage(named: named, in: Bundle.main, compatibleWith: nil) { - return image - } - - // Try from framework bundle under tags namespace - let bundle = Bundle(for: EventLayer.self) - - if let image = UIImage(named: "tags/\(named)", in: bundle, compatibleWith: nil) { - return image - } - - // Try without namespace in framework bundle - if let image = UIImage(named: named, in: bundle, compatibleWith: nil) { + // Try with "Tags/" prefix (e.g., "Tags/BED") + if let image = UIImage(named: "Tags/\(named)", in: Bundle.main, compatibleWith: nil) { return image } diff --git a/README.md b/README.md index d4c0845..7e5a371 100644 --- a/README.md +++ b/README.md @@ -196,13 +196,20 @@ Below is a table of all customizable properties of the `WeekView` ### Event Tags -Events support tags which are displayed at the bottom of event cells. Tags can be text labels or icons. +Events support tags which are displayed at the bottom of event cells. Tags can be displayed in three different ways: +1. **Icon images** from your app's Assets +2. **Emojis** (any unicode emoji) +3. **Text labels** with colored backgrounds #### Using Tags -Add tags to events by passing a string array: +Add tags to events by passing tag objects: ```swift +let bedTag = EventTag(name: "BED", color: .blue) +let emojiTag = EventTag(name: "🎉", color: .clear) +let textTag = EventTag(name: "Important", color: .red) + let event = EventData( id: "1", title: "Meeting", @@ -211,21 +218,65 @@ let event = EventData( location: "Room 101", color: .blue, allDay: false, - tags: ["Work", "Important"] + tags: [bedTag, emojiTag, textTag] ) ``` -#### Custom Tag Icons +#### Tag Types + +**Icon Tags** +- Place image files (PNG, SVG, PDF) in your app's `Assets.xcassets` +- Create an Image Set folder with the tag name (e.g., `BED.imageset`) +- Add images to the set and set them to **Universal** (not Unassigned) +- Use the **exact folder name as the tag name** in code + +The framework searches for icons in these locations: +- `Assets.xcassets/tags/TAGNAME` +- `Assets.xcassets/Tags/TAGNAME` + +Example folder structure: +``` +Assets.xcassets/ +├── Tags/ +│ ├── BED.imageset/ +│ │ ├── bed.svg +│ │ └── Contents.json +│ ├── BEER.imageset/ +│ │ ├── beer.svg +│ │ └── Contents.json +``` + +**Emoji Tags** +- Use any unicode emoji as the tag name +- Emojis are automatically detected and displayed as-is +- Emojis preserve their native colors and do not use the `color` parameter +- The `color` parameter is **ignored** for emoji tags + +Example: +```swift +EventTag(name: "🎉", color: .clear) // color parameter ignored +EventTag(name: "⭐", color: .red) // color parameter ignored +``` + +**Text Tags** +- Any text that's not an emoji and doesn't have a matching icon image +- Displays with a colored background +- Use the `color` parameter to set the background color + +Example: +```swift +EventTag(name: "Important", color: .red) +EventTag(name: "Work", color: .blue) +``` -These tags will display as icons instead of text if you add them to your app's Assets.xcassets +#### Implementation Details -To add your own custom tag icons: - - Open your app's `Assets.xcassets` - - Add a new Image Set for each icon (e.g., "meeting", "personal") - - Add images to the image sets - - Use the **image set name as the tag name** +Tags are rendered in this order: +1. Check if a matching icon image exists in Assets +2. Check if the tag name is emoji-only +3. Display as a text label with color background -Tags without matching icons will be displayed as text tags with the event color. +Icon images are displayed without text, while text and emoji tags may include additional styling based on the event color. ## How it works