skip to Main Content

Let’s say you’re writing the IOS component of a Flutter plugin in Swift.

You have your MyFlutterPlugin.swift file open in XCode.

So that we know what we’re talking about the same thing, it’s the file that looks like this:

public class MyFlutterPlugin: NSObject, FlutterPlugin {

    public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "my_flutter_plugin", binaryMessenger: registrar.messenger())
        let instance = SwiftMetronomeFlutterPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel)
    }

    public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

    // ...

    }

}

This file exists on disk at:

[your_plugin_project_name]/ios/classes/MyFlutterPlugin.swift

How could you add a resource, for example an audio (WAV) file, such that it could be referenced from within MyFlutterPlugin.swift?

In a normal (not Flutter) IOS app, I have accomplished this by simply dragging the WAV file into XCode where it sits alongside ViewController.swift (in the same folder), and referenced it from within the ViewController like this:

let clack_Path = Bundle.main.path(forResource: "clack", ofType: "wav")
let clack_URL = URL(fileURLWithPath: clack_Path!)

But, if I try that same approach from within MyFlutterPlugin.swift (putting the audio file alongside MyFlutterPlugin.swift), I get an error that clack_Path is null.

So where is the correct place to put the file, and how to properly reference it?

I can’t find any code examples, tutorials, or documentation (at least not any that I recognize as being useful) on this specific problem. Hopefully it’s actually possible to do on IOS… I’ve already got the resources included and working on the Android side of the plugin.

2

Answers


  1. Try this: https://docs.flutter.dev/development/ui/assets-and-images#loading-flutter-assets-in-ios

    In short, firstly, put your file as a normal Flutter asset (instead of ios/android asset). Then, use the approach in the link to access it.

    Login or Signup to reply.
  2. To be able to use assets in the Flutter Plugin, firstly, we need to add the resources inside the plugins’s folder, in this example, I am placing the clap.wav file in the folder [your_plugin_project_name]/ios/Assets/.

    After adding the file inside the plugin’s folder, we need to specify in the plugin’s PodSpec where the assets are located.

    # File [your_plugin_project_name]/my_flutter_plugin.podspec
    Pod::Spec.new do |s|
      # [...] supressed content above
      s.source_files = 'Classes/**/*'
      s.resources    = ['Assets/**.*']
      # [...] supressed content below
    end
    

    The important part in the snippet above is the line s.resources = ['Assets/**.*'], where Assets/**/* specify that all the plugin’s resources are inside the folder Assets. The Assets/**/* is a wildcard used to tell that every file (the * string part) in every folder/subfolder (the ** part) within the folder Assets must be included as resources of the plugin’s bundle. You can learn more about this kind of string searching about regular expression.

    Every time you change the plugin’s configuration, you’ll need to inform the Flutter’s project using the plugin that there are new changes. In a regular project, you would need to bump the plugin’s version and release a new version of it in the Pub, but as we are just changing this locally (without releasing it to the world), we need only to inform Flutter that the plugin’s files are outdated. The easiest way to do that is to run flutter clean inside the project that is using the plugin.

    After that, you will be able to access the clap.wav file in the plugins’s swift files using the code:

    guard let url = Bundle(for: type(of: self)).url(forResource: "clap", withExtension: "wav") else {
       return print("File not found") 
    }
    

    Notice that you must use Bundle(for: type(of: self)) instead of Bundle.main (the Bundle.main points to the Runner’s bundle instead of your plugin’s bundle).

    I the example below, I show how you could play the clap.wav using swift code:

    import Flutter
    import UIKit
    import AVFoundation
    
    public class SwiftMyFlutterPlugin: NSObject, FlutterPlugin {
      private var player: AVAudioPlayer?
    
      public static func register(with registrar: FlutterPluginRegistrar) {
        let channel = FlutterMethodChannel(name: "my_flutter_plugin", binaryMessenger: registrar.messenger())
        let instance = SwiftMyFlutterPlugin()
        registrar.addMethodCallDelegate(instance, channel: channel)
      }
    
      public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
        switch call.method {
        case "getPlatformVersion":
          return result("iOS " + UIDevice.current.systemVersion)
        case "playSound":
          return playSound(result: result)
        default:
          return result(FlutterMethodNotImplemented)
        }
      }
    
      private func playSound(result: @escaping FlutterResult) {
        if player?.isPlaying == true { return }
    
        let filename = "clap"
        let fileExtension = "wav"
        guard let url = Bundle(for: type(of: self)).url(forResource: filename, withExtension: fileExtension) else {
          let flutterError = FlutterError(
            code: "fileNotFound",
            message: "File not found: (filename).(fileExtension).",
            details: nil
          )
          return result(flutterError)
        }
    
        do {
          try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
          try AVAudioSession.sharedInstance().setActive(true)
        } catch {
          let flutterError = FlutterError(
            code: "audioSessionSetupError",
            message: "Error on AVAudionSession setup.",
            details: error.localizedDescription
          )
          return result(flutterError)
        }
    
        do {
          player = try AVAudioPlayer(contentsOf: url)
          player?.play()
        } catch {
          let flutterError = FlutterError(
            code: "audioPlayerSetupError",
            message: "Error on AVAudioPlayer setup.",
            details: error.localizedDescription
          )
          return result(flutterError)
        }
      }
    }
    

    The example above is a simple example of how you could play the clap.wav file, but you could do in different other ways. As this is not related with the question, I think that the example above is enough to answer your question.

    To facilitate your understanding and easily demonstrate that the code above works, I created a simple project in the GitHub that you can execute to see that this works as expected, see it here.

    Notice that you must run the code inside the folder example.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search