Skip to content

Release 4.0.0 #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 2, 2025
Merged

Release 4.0.0 #27

merged 18 commits into from
May 2, 2025

Conversation

efremidze
Copy link
Owner

@efremidze efremidze commented May 2, 2025

Checklist

Motivation and Context

Description

Summary by CodeRabbit

  • New Features

    • Introduced Core Haptics support for enhanced haptic feedback on supported devices.
    • Added a modern haptic playback system with intensity and sharpness controls.
    • Updated public API to allow selection between legacy and new haptic playback.
    • Added new semantic haptic types like start, stop, increase, decrease, success, failure, and warning.
    • Added a logging utility for informational and error messages.
  • Breaking Changes

    • Dropped support for iOS 12 and earlier; minimum supported iOS version is now 13.0.
    • Updated protocol conformance to use modern Swift conventions.
  • Documentation

    • Updated README and changelog to reflect new features and version requirements.
    • Added installation instructions for Swift Package Manager and Carthage.
  • Other Improvements

    • Improved internal handling of haptic patterns and playback scheduling for reliability and flexibility.

Copy link

coderabbitai bot commented May 2, 2025

Warning

Rate limit exceeded

@efremidze has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 13 minutes and 7 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 329017c and d6381aa.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • README.md (1 hunks)
  • Sources/Pattern.swift (4 hunks)

Walkthrough

The changes collectively update the project for a new major release (version 4.0.0), raising the minimum supported iOS version to 13.0 and introducing Core Haptics support. The codebase removes conditional checks and availability attributes related to earlier iOS versions, simplifies protocol declarations, and updates documentation and metadata to reflect the new requirements. A new haptic playback system using Core Haptics is added while maintaining legacy support, and public APIs are adjusted to expose both playback methods. The README and changelog are revised to clarify version compatibility and installation instructions.

Changes

File(s) Change Summary
CHANGELOG.md Added entry for version 4.0.0: notes Core Haptics support and iOS 12 support removal.
Example/ViewController.swift Removed iOS 13 availability checks for haptic button actions; minor whitespace changes.
Haptica.podspec Updated version to 4.0.0; raised iOS deployment target to 13.0.
Haptica.xcodeproj/project.pbxproj Raised iOS deployment target to 13.0; updated marketing version to 4.0.0 for all configs; added new source file Logs.swift.
Package.swift Updated Swift tools version to 5.9; raised iOS deployment target to 13.0.
README.md Extensively reformatted and expanded with clearer version requirements, installation instructions for Swift Package Manager and Carthage, usage examples, and new sections for sound effects and UIButton extensions.
Sources/Haptic.swift Added documentation comments; added new semantic haptic cases; extended generate() method to support new cases; removed runtime iOS 10 availability guard.
Sources/Hapticable.swift Changed protocol conformance from deprecated class to AnyObject.
Sources/Pattern.swift Added Core Haptics playback system with new Note enum and methods; preserved legacy playback with renamed LegacyNote enum; added engine lifecycle management and unified public API for pattern playback.
Sources/Logs.swift Added new logging utility HapticaLog with info and error methods controlled by an enable flag.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Haptic
    participant CoreHaptics
    participant LegacySystem

    User->>Haptic: play(pattern, delay, legacy)
    alt legacy == true
        Haptic->>LegacySystem: Parse pattern to [LegacyNote]
        LegacySystem->>LegacySystem: Enqueue operations for haptic/wait
        LegacySystem->>User: Haptic feedback (legacy)
    else
        Haptic->>CoreHaptics: Parse pattern to [Note]
        Haptic->>CoreHaptics: Prepare CHHapticEngine
        CoreHaptics->>CoreHaptics: Schedule and play haptic events
        CoreHaptics->>User: Haptic feedback (Core Haptics)
    end
Loading

Poem

🐇
Version four arrives with a haptic cheer,
Core Haptics now thumping, crisp and clear.
Old iOS checks have hopped away,
Only thirteen and up can join the play.
Docs refreshed, the code refined—
This bunny’s proud of what you’ll find!

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
Sources/Extensions.swift (1)

11-19: ⚠️ Potential issue

UIControl.Event already conforms to Hashable; this extension breaks the build

UIControl.Event (an OptionSet) automatically inherits Hashable & Equatable.
Adding:

extension UIControl.Event: @retroactive Hashable {}

causes the pipeline errors you’re seeing:

  • Unknown attribute 'retroactive'
  • Inheritance from non-protocol type 'any Hashable'

Simply remove the entire extension (and the custom == helper) – the type is hashable out-of-the-box.

-@retroactive extension UIControl.Event: Hashable {
-    public var hashValue: Int {
-        return Int(rawValue)
-    }
-}
-
-func == (lhs: UIControl.Event, rhs: UIControl.Event) -> Bool {
-    return lhs.rawValue == rhs.rawValue
-}

If you still need a custom hash for older Swift versions, use hash(into:), but that is unnecessary for the current minimum toolchain.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 12-12: Prefer using the hash(into:) function instead of overriding hashValue

(legacy_hashing)

🪛 GitHub Actions: Swift

[error] 11-11: Unknown attribute 'retroactive' used in extension declaration.


[error] 11-11: Inheritance from non-protocol type 'any Hashable' is not allowed.


[error] 11-11: Unknown attribute 'retroactive' used in extension declaration.


[error] 11-11: Inheritance from non-protocol type 'any Hashable' is not allowed.

🧹 Nitpick comments (3)
Example/ViewController.swift (1)

14-48: Consider DRY-ing up the repetitive outlet configuration

Every UIButton outlet repeats the same pattern:
<button>.addHaptic(.<style>, forControlEvents: .touchDown)

If more styles are added later, boiler-plate will grow. A tiny helper keeps the controller leaner and less error-prone:

class ViewController: UIViewController {

    @IBOutlet private var hapticButtons: [UIButton]!   // hook up all 8 buttons

    override func viewDidLoad() {
        super.viewDidLoad()

        let styles: [Haptic] = [
            .selection,
            .impact(.light), .impact(.medium), .impact(.heavy),
            .impact(.soft),  .impact(.rigid),
            .notification(.success), .notification(.warning), .notification(.error)
        ]

        zip(hapticButtons, styles).forEach { $0.addHaptic($1, forControlEvents: .touchDown) }
    }
}

That single loop replaces 9 separate didSet blocks and makes future maintenance trivial.
No functional change, purely readability and maintainability.

Sources/Pattern.swift (2)

9-10: Duplicate import CoreHaptics directive

CoreHaptics is imported twice (lines 9 and 94).
This is harmless but noisy and picked up by SwiftLint.

-import CoreHaptics   // ← keep this one-import CoreHaptics   // ← remove

Also applies to: 94-95


170-191: Sanity-check haptic parameter ranges

Core Haptics expects intensity & sharpness between 0 … 1.
Your mapping currently stays within range – good – but any future edit risks going out of bounds, which throws at runtime.

Safeguard with min(max(value,0),1) or assert in init?:

case .haptic(let i, let s):
    let intensity  = max(0, min(i, 1))
    let sharpness  = max(0, min(s, 1))

This turns a crash into a benign clamp.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f9e9f9 and c174668.

📒 Files selected for processing (10)
  • CHANGELOG.md (1 hunks)
  • Example/ViewController.swift (2 hunks)
  • Haptica.podspec (1 hunks)
  • Haptica.xcodeproj/project.pbxproj (4 hunks)
  • Package.swift (1 hunks)
  • README.md (1 hunks)
  • Sources/Extensions.swift (1 hunks)
  • Sources/Haptic.swift (0 hunks)
  • Sources/Hapticable.swift (1 hunks)
  • Sources/Pattern.swift (6 hunks)
💤 Files with no reviewable changes (1)
  • Sources/Haptic.swift
🧰 Additional context used
🧬 Code Graph Analysis (1)
Example/ViewController.swift (1)
Sources/Hapticable.swift (1)
  • addHaptic (50-54)
🪛 GitHub Actions: Swift
Sources/Extensions.swift

[error] 11-11: Unknown attribute 'retroactive' used in extension declaration.


[error] 11-11: Inheritance from non-protocol type 'any Hashable' is not allowed.


[error] 11-11: Unknown attribute 'retroactive' used in extension declaration.


[error] 11-11: Inheritance from non-protocol type 'any Hashable' is not allowed.

🪛 SwiftLint (0.57.0)
Sources/Pattern.swift

[Warning] 94-94: Imports should be unique

(duplicate_imports)

🔇 Additional comments (6)
Sources/Hapticable.swift (1)

15-15: Modernize class-only protocol declaration
Replacing the deprecated class constraint with AnyObject is the correct approach in modern Swift (5+). This aligns with the updated iOS 13 deployment target and Swift tools version without any change in functionality.

CHANGELOG.md (1)

3-7: Verify dropped platform support consistency
The changelog notes “Dropped iOS 12 Support” (min iOS 13.0), so please ensure all project targets (Package.swift, Xcode project, Podspec) and documentation reflect iOS 13.0 as the minimum.

README.md (1)

18-18: Minimum deployment target updated correctly
The README now accurately states that Haptica 4.x requires iOS 13.0+ while 3.x supports iOS 9.0+. This matches the rest of the project.

Package.swift (1)

1-1: Swift tools version and platform target bump are correct
The swift-tools-version:5.9 directive and platforms: [.iOS(.v13)] align with CoreHaptics requirements and the raised deployment target. Ensure your CI and Xcode project settings are in sync.

Also applies to: 9-9

Haptica.podspec (1)

11-11: Podspec version bumped for 4.0.0 release
Updating s.version to '4.0.0' is correct for this release.

Haptica.xcodeproj/project.pbxproj (1)

359-363: Deployment-target mismatch between framework and Example app

The framework target’s IPHONEOS_DEPLOYMENT_TARGET is bumped to 13.0 (👍),
but the Example app target inherits the old value (not explicitly set).

On Xcode ≤14 this results in:

ld: warning: object file compiled with newer iOS version (13.0) than being linked (9.0)

and the app may crash at runtime when .soft / .rigid haptics are used.

Please set the same deployment target on the Example target (or remove the explicit setting on the framework so both inherit).
No code change required – just update the build-setting.

Also applies to: 417-423

Comment on lines 96 to 133
public extension Haptic {
static var engine: CHHapticEngine?

static func prepareHaptics() {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

do {
engine = try CHHapticEngine()
try engine?.start()

// The engine stops automatically when it goes into the background
engine?.stoppedHandler = { reason in
print("Haptic engine stopped: \(reason)")
}

engine?.resetHandler = {
print("Haptic engine reset")
do {
try engine?.start()
} catch {
print("Failed to restart engine: \(error)")
}
}
} catch {
print("Failed to create haptic engine: \(error)")
}
}

static func play(_ notes: [Note]) {
guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }

if engine == nil {
prepareHaptics()
}

guard let engine else { return }

do {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential data race on the shared CHHapticEngine

engine is a static mutable singleton that is:

  1. lazily created in prepareHaptics()
  2. accessed & mutated from play(_:) (which may be called from any thread)

Because CHHapticEngine is not thread-safe, simultaneous calls to Haptic.play from multiple threads could:

  • create two engines,
  • race on engine.start(),
  • or read a tearing pointer.

A lightweight fix is to wrap engine access in a serial queue:

private static let engineQueue = DispatchQueue(label: "Haptica.engine")

static func prepareHaptics() {
    engineQueue.sync {
        guard engine == nil else { return }
        guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
        
    }
}

static func play(_ notes: [Note]) {
    engineQueue.sync {
        if engine == nil { prepareHaptics() }
        guard let engine else { return }
        
    }
}

This keeps the public API unchanged while eliminating the race window.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
README.md (1)

121-124: Clarify separation of SPM dependency blocks

Having two dependencies: arrays in the same fragment can be confusing. Consider labeling or splitting these into two distinct code fences—one for iOS 13+ (4.x) and one for iOS 9 (3.x)—so users know to choose the appropriate snippet.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c174668 and 473458a.

📒 Files selected for processing (1)
  • README.md (3 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
README.md

141-141: Bare URL used
null

(MD034, no-bare-urls)


144-144: Bare URL used
null

(MD034, no-bare-urls)

🔇 Additional comments (3)
README.md (3)

18-18: Verify consistency of minimum iOS version across Podspec and README

The README states Haptica 4.x requires iOS 13.0+, but the Podspec was updated to iOS 12.0. Ensure these minimum deployment targets are aligned across documentation and Podspec to avoid confusion.


111-111: Great addition of Swift Package Manager section

The new ### Swift Package Manager header and introductory text clearly guide users on adding Haptica via SPM.


116-119: Correct SPM snippet for iOS 13+ projects

The package declaration for iOS 13+ (.package(url: "https://github.com/efremidze/Haptica.git", from: "4.0.0")) is accurate and follows SPM conventions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
Sources/Pattern.swift (1)

27-29: ⚠️ Potential issue

Fix potential data race on shared CHHapticEngine.

The static engine property is accessed from multiple places without thread synchronization. Since CHHapticEngine is not thread-safe, this could lead to race conditions if play(_:) is called from multiple threads simultaneously.

Implement a thread-safe approach using a serial queue:

+    private static let engineQueue = DispatchQueue(label: "Haptica.engine")
+
     static var engine: CHHapticEngine?
     
     static func prepareHaptics() {
+        engineQueue.sync {
         guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
         
         do {
             engine = try CHHapticEngine()
             try engine?.start()
             
             engine?.resetHandler = {
                 do {
                     try engine?.start()
                 } catch {
                     HapticaLog.error("Failed to restart engine: \(error)")
                 }
             }
         } catch {
             HapticaLog.error("Failed to create haptic engine: \(error)")
         }
+        }
     }

Apply similar changes to the play(_:) method as well.

README.md (1)

149-153: 🛠️ Refactor suggestion

Fix Carthage installation instructions.

The current Carthage section doesn't specify version constraints for different iOS targets.

 ### Carthage
 
 ```ruby
-github "efremidze/Haptica"
+# For Haptica 3.x (iOS 9+)
+github "efremidze/Haptica" ~> 3.0
+
+# For Haptica 4.x (iOS 13+)
+github "efremidze/Haptica" ~> 4.0

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>Sources/Logs.swift (2)</summary><blockquote>

`9-9`: **Consider using OS Logger API instead of just importing the module.**

You're importing `os.log` but only using basic `print` statements. For better integration with the system logging framework and improved performance, consider using the actual OS Logger API.


```diff
-import os.log
+import os
+
+// Define a subsystem and category for your logs
+private let logger = Logger(subsystem: "com.efremidze.haptica", category: "haptics")

And then in your logging methods:

 static func info(_ message: String) {
-    if isEnabled { print("[Haptica] \(message)") }
+    if isEnabled { logger.info("[\(message)]") }
 }

12-12: Implement conditional compilation for debug logging.

Your comment suggests conditionally compiling with a DEBUG flag, but this isn't implemented. Using actual conditional compilation would be more efficient.

-    static var isEnabled = true // or conditionally compile with DEBUG flag
+    #if DEBUG
+    static var isEnabled = true
+    #else
+    static var isEnabled = false
+    #endif
Sources/Pattern.swift (5)

49-90: Consider handling engine state more robustly.

The current implementation creates a new engine if it's nil, but doesn't handle the case where the engine might be in a stopped state or might have failed.

 static func play(_ notes: [Note]) {
     guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
     
-    if engine == nil {
-        prepareHaptics()
-    }
-    
-    guard let engine else { return }
+    // Get or create engine with proper error handling
+    guard let currentEngine = engineQueue.sync(execute: { () -> CHHapticEngine? in
+        if engine == nil {
+            prepareHaptics()
+        }
+        
+        if let engine = engine, engine.state != .stopped {
+            return engine
+        } else if let engine = engine {
+            do {
+                try engine.start()
+                return engine
+            } catch {
+                HapticaLog.error("Failed to start engine: \(error)")
+                return nil
+            }
+        }
+        return nil
+    }) else { return }
     
     do {
         var events = [CHHapticEvent]()
         var currentTime: TimeInterval = 0
         
         for note in notes {
             switch note {
             case .haptic(let intensity, let sharpness):
                 // Create and add haptic event
                 let event = CHHapticEvent(
                     eventType: .hapticTransient,
                     parameters: [
                         CHHapticEventParameter(parameterID: .hapticIntensity, value: intensity),
                         CHHapticEventParameter(parameterID: .hapticSharpness, value: sharpness)
                     ],
                     relativeTime: currentTime
                 )
                 events.append(event)
                 currentTime += 0.1 // Default duration for transient events
                 
             case .wait(let interval):
                 currentTime += interval
             }
         }
         
         // Create pattern from events
         let pattern = try CHHapticPattern(events: events, parameters: [])
         
         // Create player and play pattern
-        let player = try engine.makePlayer(with: pattern)
+        let player = try currentEngine.makePlayer(with: pattern)
         try player.start(atTime: 0)
     } catch {
         HapticaLog.error("Failed to play pattern: \(error)")
     }
 }
🧰 Tools
🪛 GitHub Actions: Swift

[error] 89-89: Cannot find 'HapticaLog' in scope at line 89.


114-114: Complete the TODO comment with a proper description.

Empty TODO comments should include a description of what needs to be done.

-/// TODO
+/// Represents a haptic note with customizable intensity and sharpness,
+/// or a timed wait interval for pattern-based haptic feedback.
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 114-114: TODOs should be resolved

(todo)


139-139: Complete the TODO comment with a proper description.

Empty TODO comments should include a description of what needs to be done.

-/// TODO
+/// Represents a legacy haptic note using predefined impact types,
+/// or a timed wait interval for pattern-based haptic feedback.
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 139-139: TODOs should be resolved

(todo)


168-178: Document why Sendable conformance is force-unwrapped.

When using @unchecked Sendable, it's important to document why the type is considered thread-safe even though the compiler cannot verify it.

+/**
+ * A operation that generates haptic feedback.
+ *
+ * This class is marked as @unchecked Sendable because:
+ * - All member variables are immutable
+ * - The haptic generation is synchronized to the main thread
+ * - No mutable state is shared between threads
+ */
 class HapticOperation: Operation, @unchecked Sendable {
     let haptic: Haptic
     init(_ haptic: Haptic) {
         self.haptic = haptic
     }
     override func main() {
         DispatchQueue.main.sync {
             self.haptic.generate()
         }
     }
 }

180-188: Document why Sendable conformance is force-unwrapped.

Similar to the HapticOperation class, document why the WaitOperation is considered thread-safe.

+/**
+ * An operation that introduces a timed wait interval.
+ *
+ * This class is marked as @unchecked Sendable because:
+ * - All member variables are immutable
+ * - Thread.sleep is thread-safe
+ * - No mutable state is shared between threads
+ */
 class WaitOperation: Operation, @unchecked Sendable {
     let duration: TimeInterval
     init(_ duration: TimeInterval) {
         self.duration = duration
     }
     override func main() {
         Thread.sleep(forTimeInterval: duration)
     }
 }
README.md (1)

81-87: Document the benefits of Core Haptics.

While you mention Core Haptics support, it would be helpful to explain the benefits of using Core Haptics over the legacy API.

 ### 🔧 Core Haptics Support
 
-Haptica uses **Core Haptics** by default. To use the legacy API:
+Haptica uses **Core Haptics** by default, which offers:
+
+- More precise control over haptic intensity and sharpness
+- Support for complex haptic patterns and timing
+- Better integration with Apple's latest haptic hardware
+
+To use the legacy API for backward compatibility:
 
 ```swift
 Haptic.play(notes, legacy: true)

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 473458a71a103ed3d3b772c7ca1c30f5293abd0f and 0dd1fadd15b0673d043df7305b2e39b9304125ff.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `Haptica.podspec` (1 hunks)
* `README.md` (1 hunks)
* `Sources/Logs.swift` (1 hunks)
* `Sources/Pattern.swift` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Haptica.podspec

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>Sources/Pattern.swift (1)</summary><blockquote>

<details>
<summary>Sources/Logs.swift (1)</summary>

* `error` (18-20)

</details>

</blockquote></details>

</details><details>
<summary>🪛 SwiftLint (0.57.0)</summary>

<details>
<summary>Sources/Pattern.swift</summary>

[Warning] 114-114: TODOs should be resolved

(todo)

---

[Warning] 139-139: TODOs should be resolved

(todo)

</details>

</details>
<details>
<summary>🪛 GitHub Actions: Swift</summary>

<details>
<summary>Sources/Pattern.swift</summary>

[error] 41-41: Cannot find 'HapticaLog' in scope at line 41.

---

[error] 45-45: Cannot find 'HapticaLog' in scope at line 45.

---

[error] 89-89: Cannot find 'HapticaLog' in scope at line 89.

</details>

</details>
<details>
<summary>🪛 markdownlint-cli2 (0.17.2)</summary>

<details>
<summary>README.md</summary>

15-15: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>README.md (1)</summary>

`22-27`: **LGTM! Clear version compatibility table.**

The version compatibility table clearly shows that Haptica 4.x requires iOS 13+, which aligns with the Core Haptics support introduced in this release.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
README.md (1)

166-168: Fixed Carthage section as requested in previous review.

The Carthage section has been simplified with the correct syntax.

Sources/Pattern.swift (1)

33-56: ⚠️ Potential issue

Potential data race on the shared CHHapticEngine.

The static engine property is accessed and modified from multiple methods without synchronization. This could lead to thread safety issues if play is called concurrently from different threads.

Implement thread-safety for engine access:

+private static let engineQueue = DispatchQueue(label: "Haptica.engine")

 static func prepareHaptics() {
+    engineQueue.sync {
     guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
     
     do {
         engine = try CHHapticEngine()
         try engine?.start()
         
         engine?.resetHandler = {
             do {
                 try engine?.start()
             } catch {
                 HapticaLog.error("Failed to restart engine: \(error)")
             }
         }
     } catch {
         HapticaLog.error("Failed to create haptic engine: \(error)")
     }
+    }
 }

And similarly protect engine access in the play method:

 static func play(_ notes: [Note]) {
+    engineQueue.sync {
     guard CHHapticEngine.capabilitiesForHardware().supportsHaptics else { return }
     
     if engine == nil {
         prepareHaptics()
     }
     
     guard let engine else { return }
     
     // ... rest of method ...
+    }
 }
🧹 Nitpick comments (1)
README.md (1)

10-17: Improved introduction and project description.

The updated intro clearly positions Haptica as a "simple and expressive haptic feedback generator for iOS" and includes a proper pod try command.

The console command could be updated to show expected output as per markdownlint suggestions:

-```bash
-$ pod try Haptica
-```
+```console
+$ pod try Haptica
+# This will install and open the Haptica example project
+```
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

15-15: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dd1fad and 329017c.

📒 Files selected for processing (4)
  • Haptica.xcodeproj/project.pbxproj (8 hunks)
  • README.md (1 hunks)
  • Sources/Haptic.swift (1 hunks)
  • Sources/Pattern.swift (4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
Sources/Pattern.swift (1)
Sources/Logs.swift (1)
  • error (18-20)
🪛 markdownlint-cli2 (0.17.2)
README.md

15-15: Dollar signs used before commands without showing output
null

(MD014, commands-show-output)

🔇 Additional comments (18)
Haptica.xcodeproj/project.pbxproj (3)

21-21: Added logging capabilities to the framework.

The new Logs.swift file has been properly integrated into the project structure, providing a centralized logging mechanism for the framework.

Also applies to: 67-67, 124-124, 268-268


363-363: Raised minimum iOS deployment target to iOS 13.0.

This is a breaking change that enables Core Haptics usage but drops support for iOS 9-12. This aligns with the major version increment (4.0.0) and matches the requirements documented in the README.

Make sure this change is clearly communicated in your release notes and CHANGELOG so users are aware of the breaking change when upgrading.

Also applies to: 421-421


445-445: Updated marketing version to 4.0.0.

Version bump appropriately reflects the breaking changes in minimum iOS version requirement and the addition of Core Haptics support.

Also applies to: 465-465

Sources/Haptic.swift (4)

11-15: Improved documentation for haptic feedback types.

Adding clear documentation comments improves API clarity and helps developers understand the purpose of each haptic feedback style.


23-26: Enhanced documentation for notification feedback types.

Good clarification about the intended usage of notification feedback types for task outcomes.


34-48: Added semantic haptic variants.

The new semantic variants (start, stop, increase, decrease, success, failure, warning) provide more expressive, domain-specific haptic feedback options. This enhances the API's usability by making code more readable and intention-revealing.


67-82: Implemented semantic variant mappings.

The implementation elegantly maps each semantic variant to an appropriate underlying haptic feedback type, maintaining compatibility while expanding the API surface.

README.md (5)

22-27: Added comprehensive version compatibility table.

The requirements table clearly communicates which versions of Haptica support different iOS, Swift, and Xcode versions, making it easier for users to choose the appropriate version.


54-68: Documented new semantic haptic types.

The documentation for the new semantic types matches the implementation in Haptic.swift and provides clear examples of how to use these expressive variants.


96-102: Added Core Haptics support documentation.

Clear explanation of the new Core Haptics support with instructions on how to use the legacy API if needed.


149-155: Updated Swift Package Manager installation instructions.

The SPM instructions now include version-specific guidance for iOS 13+ (v4.0.0) and iOS 9 (v3.0.0), which is helpful for users who need to support older iOS versions.


188-189: Improved license information with direct link.

The license section now includes a direct link to the LICENSE file, making it easier for users to access the full license text.

Sources/Pattern.swift (6)

9-28: Added dual-API support for both Core Haptics and legacy feedback.

The enhanced play method now supports both Core Haptics (default) and legacy UIKit-based haptic feedback via a legacy parameter. This maintains backward compatibility while providing access to new functionality.


61-103: Implemented Core Haptics pattern player.

The new implementation creates a sequence of haptic events with precise control over intensity, sharpness, and timing parameters. This provides much more expressive haptic feedback compared to the legacy API.


107-126: Organized legacy haptic feedback as a separate component.

Good separation of concerns by isolating the legacy haptic functionality in its own extension. This maintains backward compatibility while keeping the code organized.


133-160: Enhanced Note enum with precise haptic parameters.

The updated Note enum now supports explicit intensity and sharpness values for Core Haptics, providing much finer control over the haptic experience compared to the legacy API.


162-183: Added hapticNote mapping for semantic types.

This extension elegantly maps the semantic haptic types to appropriate CoreHaptics parameters, ensuring consistent behavior across both APIs.


221-231: Added @unchecked Sendable to operation classes.

The @unchecked Sendable attribute allows these classes to be used in concurrent contexts. However, make sure the classes are actually thread-safe.

Verify that these classes properly handle their mutable state in concurrent environments. The HapticOperation class correctly uses DispatchQueue.main.sync to ensure thread-safety when generating haptic feedback.

Also applies to: 234-242

@efremidze efremidze merged commit b33bb4a into master May 2, 2025
2 checks passed
@efremidze efremidze deleted the release-4.0.0 branch May 2, 2025 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant