Skip to content

Conversation

@m1guelpf
Copy link

@m1guelpf m1guelpf commented Jun 11, 2025

Warning

This PR is not currently functional, and is more intended as a showcase of what's possible than a real feature that might get merged any time soon.

What does this do?

This PR adds a SwiftPM/Xcode plugin which, when enabled, will try to parse all .riv files on the project's directory and auto-generate types to work with them.

This is achieved by adding a rive-codegen cli tool (which takes care of generating the types), and a SwiftPM/Xcode "build tool", which automatically runs the plugin at build time and makes the generated types available to your project. The generated file is not written to the filesystem (preventing outdated types), but can be inspected in Xcode.

What DX could this enable?

(This is obviously subjective, and represents my personal opinion, which is why it's collapsed)

// imagine a `star-rating.riv` file with a StarRating artboard, which
// has a ViewModel with a `score` enum and a `completed` trigger.

// a codegen approach could enable writing the code below with
// no other prep than adding the `.riv` file to the project.

import SwiftUI

struct ContentView: View {
	@State private var rating = Rive.StarRating()

	var body: some View {
		VStack {
			rating.view()
		}
		.onChange(of: rating.score) { score in
			if score == .max {
				rating.completed.trigger()
			}
		}
	}
}

What is currently missing?

⚠️ Fixing CLI builds inside of the plugin runtime

Currently, the rive-codegen cli builds and works as expected when it's ran independently (via swift build or the rive-codegen target in Xcode. However, when running the plugin, the CLI is not able to find the RiveRuntime framework, and fails to run.

Error logs
PhaseScriptExecution Running\ rive-codegen /Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/AudioDiary.build/Debug-iphoneos/AudioDiary.build/Script-C0843F312DFA2B0900B5EA66.sh (in target 'AudioDiary' from project 'AudioDiary')
    cd /Users/m1guelpf/Code/Quickies/AudioDiary
    /bin/sh -c /Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/AudioDiary.build/Debug-iphoneos/AudioDiary.build/Script-C0843F312DFA2B0900B5EA66.sh

dyld[9266]: Library not loaded: @rpath/RiveRuntime.framework/Versions/A/RiveRuntime
  Referenced from: <9F23A3F4-CF63-32E9-97D5-3770BD1282BC> /Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/rive-codegen
  Reason: tried: '/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file)
/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/AudioDiary.build/Debug-iphoneos/AudioDiary.build/Script-C0843F312DFA2B0900B5EA66.sh: line 19:  9266 Abort trap: 6           /usr/bin/sandbox-exec -p "(version 1)
(deny default)
(import \"system.sb\")
(allow file-read*)
(allow process*)
(allow mach-lookup (global-name \"com.apple.lsd.mapdb\"))
(allow file-write*
    (subpath \"/private/tmp\")
    (subpath \"/private/var/folders/sk/zfx2h9812ds04tv8n14wngmm0000gn/T\")
)
(deny file-write*
    (subpath \"/Users/m1guelpf/Code/Quickies/AudioDiary\")
)
(allow file-write*
    (subpath \"/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/BuildToolPluginIntermediates/AudioDiary.output/AudioDiary/RivePlugin\")
    (subpath \"/private/var/folders/sk/zfx2h9812ds04tv8n14wngmm0000gn/T/TemporaryItems\")
)
" "/${BUILD_DIR}/${CONFIGURATION}/rive-codegen" generate "file:///Users/m1guelpf/Code/Quickies/AudioDiary/src/Resources/lock-screen.riv" --output-directory "file:///Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/BuildToolPluginIntermediates/AudioDiary.output/AudioDiary/RivePlugin/GeneratedSources"
Command PhaseScriptExecution failed with a nonzero exit code

dyld[9266]: Library not loaded: @rpath/RiveRuntime.framework/Versions/A/RiveRuntime

  Referenced from: <9F23A3F4-CF63-32E9-97D5-3770BD1282BC> /Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/rive-codegen

  Reason: tried: '/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Products/Debug/PackageFrameworks/RiveRuntime.framework/Versions/A/RiveRuntime' (no such file)

/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/AudioDiary.build/Debug-iphoneos/AudioDiary.build/Script-C0843F312DFA2B0900B5EA66.sh: line 19:  9266 Abort trap: 6           /usr/bin/sandbox-exec -p "(version 1)

(deny default)

(import \"system.sb\")

(allow file-read*)

(allow process*)

(allow mach-lookup (global-name \"com.apple.lsd.mapdb\"))

(allow file-write*

    (subpath \"/private/tmp\")

    (subpath \"/private/var/folders/sk/zfx2h9812ds04tv8n14wngmm0000gn/T\")

)

(deny file-write*

    (subpath \"/Users/m1guelpf/Code/Quickies/AudioDiary\")

)

(allow file-write*

    (subpath \"/Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/BuildToolPluginIntermediates/AudioDiary.output/AudioDiary/RivePlugin\")

    (subpath \"/private/var/folders/sk/zfx2h9812ds04tv8n14wngmm0000gn/T/TemporaryItems\")

)

" "/${BUILD_DIR}/${CONFIGURATION}/rive-codegen" generate "file:///Users/m1guelpf/Code/Quickies/AudioDiary/src/Resources/lock-screen.riv" --output-directory "file:///Users/m1guelpf/Library/Developer/Xcode/DerivedData/AudioDiary-bkgrwsphjrdtredonlpqmpxkmgqq/Build/Intermediates.noindex/BuildToolPluginIntermediates/AudioDiary.output/AudioDiary/RivePlugin/GeneratedSources"

Command PhaseScriptExecution failed with a nonzero exit code

The reason for this seems to be that the CLI looks for RiveRuntime.xcframework inside PackageFrameworks, while the framework is outside.

I haven't been able to find a way to fix this (but like, there has to be, right?) and haven't been able to find any other repos on GitHub search using a CLI-based plugin that requires an xcframework.

The lazy way around this would be to distribute the build rive-codegen binary as a binaryTarget, which seems relatively common (widely used for SwiftLin or SwiftGen) so shouldn't run into the same issue.

Naming for Enums (and ViewModels)

There seems to be no way to get the Rive name for an Enum. The name seems to be somewhere inside the .riv file (strings file.riv | grep EnumName works), but I can't find any APIs for reading it. Exposing it as part of RiveDataBindingViewModel.Instance.EnumProperty would work best.

Same for ViewModels. Those I can at least get a name for by building a dictionary via riveFile.viewModel(at:), but exposing the name at RiveDataBindingViewModel.Instance would make life easier.

The rest of the glue code

The code in this PR currently just generates structs and enums for data binding for each artboard. Once the above issues are fixed, you'd ideally also want to generate RiveViewModel classes, and get/set accessors on those to manipulate the properties directly. And, if you wanna be fancy, hook up listeners to the Observable framework, to listen for changes in a more intuitive way.

I don't expect any of these to be hard at all once the above issues (specifically the build issue) are fixed.

Other fixes

  • Re-add a Package@swift-5.9.swift to maintain Swift 5.9 compatibility
  • Probably move the RiveRuntime source from ./Source to ./Sources/RiveFramework
  • see the rest of the TODOs in code

@dskuza
Copy link
Collaborator

dskuza commented Jun 12, 2025

This is a super cool idea, and something I think is worth further investigating / adding to the project. Thanks for opening the PR, I'll definitely dig further into your implementation and see if anything can carry over.

@cerupcat
Copy link

@m1guelpf did you look into using the WIP Rive Code Generator https://github.com/rive-app/rive-code-generator-wip/tree/add-data-binding? Rather than making a swift specific one, it could make sense to use this one which already supports a lot of funcanality cross platform and use it as the plugin instead.

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.

3 participants