Since Fyne version `v2.3.0` it has been possible to call native code for iOS and macOS using a new RunNative function. This feature opens access to any of the APIs you might want to use from the official Apple SDKs. This blog post shows how some Go and C code can be used to make native calls in your application.

Structuring the code

In this example we assume that you want to create a feature that looks up the device name – this function lookupName could be used to pass into a NewLabel or a Label.SetText to display device information in your app. It is recommended to set up 3 files to keep this easy to read – two .go files, one for use on iOS and a fallback for other devices, and a .c file that includes the non-Go code for iOS lookups.

  • lookup_ios.go – This file contains the Go code for making the request to iOS Objective-C code
  • lookup_other.go – The fallback file that does not use any iOS features for use on other devices
  • ios.c – C code for connecting to the iOS SDK.

Next we look at what each of these files should contain.

Creating the API

Firstly we should write the Go code that we will use to get the data from iOS. It opens with a build tag in case you decide to use a different filename or an older Go version (any file ending _ios.go will only be compiled for an iOS device anyway – since Go 1.16).

As you can see this introduces some CGo code that declares a iosName method that will be implemented later. The key here is that we use driver.RunNative to execute our Go in the context of the iOS App runtime. We can ignore the Context passed in, as the code will be running correctly inside this function. This simply calls through to some Objective-C code that we will write later

Note that we also need to convert the C const char * type of string to a Go string using the C.GoString function.

//go:build ios

package main

import "fyne.io/fyne/v2/driver"

/*
const char *iosName();
*/
import "C"

func lookupName() (name string) {
    driver.RunNative(func(ctx interface{}) error {
        str := C.iosName()
        name = C.GoString(str)
        return nil
    })

    return name
}

Now we need a fallback that will run if it’s a different device type we are running on. The contents of this file are a simple empty method that matches the one we created above. There is a build directive at the top that ensures this will only build on platforms that are not iOS.

//go:build !ios

package main

func lookupName() (name string) {
    return "unsupported"
}

Connecting to the iOS SDK

The file we need is for the Objective-C code we will use to connect into the iOS APIs. If you have ever programmed against Apple’s UIKit API This will look familiar – but let’s step through this code for everyone else.

This is split into two snippets for ease of understanding. This first adds the headers and C function for accessing the name of the device. In this method we simply make calls to UIDevice to get information about the device. To return the data in a C format we then call UTF8String which gets the data out of the object and returns a regular const char * type which C uses for strings.

//go:build ios

#import <UIKit/UIKit.h>

char *iosName() {
    NSString *model = [[UIDevice currentDevice] model];
    NSString *system = [[UIDevice currentDevice] systemName];
    NSString *version = [[UIDevice currentDevice] systemVersion];
    return [[NSString stringWithFormat:@"%@: %@ v%@", model, system, version] UTF8String];
}

And that’s all there is to it… It is unfortunately not pure Go, but it does demonstrate that your mobile apps built with Go and Fyne can access platform specific APIs that are not available through Go libraries.

The full source code is available on GitHub.

Discover more from Fyne Labs

Subscribe now to keep reading and get access to the full archive.

Continue reading