├── README.md ├── app ├── AppDelegate.go ├── AppDelegate.h ├── AppDelegate.m ├── GoAppViewController.h ├── GoAppViewController.m ├── handlers.go ├── main.go └── main.m └── example ├── .gitignore ├── Makefile ├── main.go ├── main.xcodeproj └── project.pbxproj └── main ├── Images.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Info.plist ├── Main.storyboard └── assets └── .gitnoignore /README.md: -------------------------------------------------------------------------------- 1 | iOS-go [![Go Report Card](https://goreportcard.com/badge/github.com/xlab/ios-go)](https://goreportcard.com/report/github.com/xlab/ios-go) 2 | ------ 3 | 4 | 5 | 6 | ⚠️ See an [example app](/example). 7 | 8 | ⚠️⚠️ Also an Vulkan API app using Metal surface and MoltenVK: 9 | https://github.com/vulkan-go/demos/tree/master/vulkandraw/vulkandraw_ios 10 | 11 | Release-ready, but I'm too lazy to write enough docs for release. Stay tuned. 12 | 13 | ## License 14 | 15 | MIT 16 | -------------------------------------------------------------------------------- /app/AppDelegate.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func init() { 10 | // Lock the goroutine responsible for initialization to an OS thread. 11 | // This means the goroutine running main (and calling the run function 12 | // below) is locked to the OS thread that started the program. This is 13 | // necessary for the correct delivery of UIKit events to the process. 14 | // 15 | // A discussion on this topic: 16 | // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ 17 | runtime.LockOSThread() 18 | } 19 | 20 | type Signal struct{} 21 | 22 | type AppDelegate interface { 23 | InitDone() 24 | LifecycleEvents() <-chan LifecycleEvent 25 | VSync() <-chan Signal 26 | 27 | HandleConfigurationEvents(out chan<- ConfigurationEvent) 28 | HandleTouchEvents(out chan<- TouchEvent) 29 | } 30 | 31 | var defaultApp = &appDelegate{ 32 | lifecycleEvents: make(chan LifecycleEvent), 33 | vsyncEvents: make(chan Signal), 34 | maxDispatchTime: 1 * time.Second, 35 | 36 | initWG: new(sync.WaitGroup), 37 | mux: new(sync.RWMutex), 38 | } 39 | 40 | type appDelegate struct { 41 | // lifecycleEvents must be handled in real-time. 42 | lifecycleEvents chan LifecycleEvent 43 | // vsyncEvents must be handled in real-time. 44 | vsyncEvents chan Signal 45 | 46 | // maxDispatchTime sets the maximum time the send operation 47 | // allowed to wait while channel is blocked. 48 | maxDispatchTime time.Duration 49 | // channels below are optional and will be sent to only 50 | // if handled by an external client. 51 | 52 | configurationEvents chan<- ConfigurationEvent 53 | touchEvents chan<- TouchEvent 54 | 55 | initWG *sync.WaitGroup 56 | mux *sync.RWMutex 57 | } 58 | 59 | func (a *appDelegate) InitDone() { 60 | a.initWG.Done() 61 | } 62 | 63 | func (a *appDelegate) LifecycleEvents() <-chan LifecycleEvent { 64 | return a.lifecycleEvents 65 | } 66 | 67 | func (a *appDelegate) VSync() <-chan Signal { 68 | return a.vsyncEvents 69 | } 70 | 71 | func (a *appDelegate) HandleConfigurationEvents(out chan<- ConfigurationEvent) { 72 | a.mux.Lock() 73 | a.configurationEvents = out 74 | a.mux.Unlock() 75 | } 76 | 77 | func (a *appDelegate) getConfigurationEventsOut() chan<- ConfigurationEvent { 78 | a.mux.RLock() 79 | out := a.configurationEvents 80 | a.mux.RUnlock() 81 | return out 82 | } 83 | 84 | func (a *appDelegate) HandleTouchEvents(out chan<- TouchEvent) { 85 | a.mux.Lock() 86 | a.touchEvents = out 87 | a.mux.Unlock() 88 | } 89 | 90 | func (a *appDelegate) getTouchEventsOut() chan<- TouchEvent { 91 | a.mux.RLock() 92 | out := a.touchEvents 93 | a.mux.RUnlock() 94 | return out 95 | } 96 | -------------------------------------------------------------------------------- /app/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AppDelegate : UIResponder 4 | 5 | @property (strong, nonatomic) UIWindow *window; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /app/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #include "_cgo_export.h" 3 | 4 | @interface AppDelegate () 5 | 6 | @end 7 | 8 | @implementation AppDelegate 9 | 10 | 11 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 12 | // Override point for customization after application launch. 13 | return YES; 14 | } 15 | 16 | - (void)applicationWillResignActive:(UIApplication *)application { 17 | // Sent when the application is about to move from active to inactive state. 18 | // This can occur for certain types of temporary interruptions (such as an 19 | // incoming phone call or SMS message) or when the user quits the 20 | // application and it begins the transition to the background state. Use 21 | // this method to pause ongoing tasks, disable timers, and throttle down 22 | // OpenGL ES frame rates. Games should use this method to pause the game. 23 | onApplicationWillResignActive(); 24 | } 25 | 26 | - (void)applicationDidEnterBackground:(UIApplication *)application { 27 | // Use this method to release shared resources, save user data, invalidate 28 | // timers, and store enough application state information to restore your 29 | // application to its current state in case it is terminated later. If your 30 | // application supports background execution, this method is called instead 31 | // of applicationWillTerminate: when the user quits. 32 | onApplicationDidEnterBackground(); 33 | } 34 | 35 | - (void)applicationWillEnterForeground:(UIApplication *)application { 36 | // Called as part of the transition from the background to the inactive 37 | // state; here you can undo many of the changes made on entering the 38 | // background. 39 | onApplicationWillEnterForeground(); 40 | } 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application { 43 | // Restart any tasks that were paused (or not yet started) while the 44 | // application was inactive. If the application was previously in the 45 | // background, optionally refresh the user interface. 46 | onApplicationDidBecomeActive(); 47 | } 48 | 49 | - (void)applicationWillTerminate:(UIApplication *)application { 50 | // Called when the application is about to terminate. Save data if 51 | // appropriate. See also applicationDidEnterBackground. 52 | onApplicationWillTerminate(); 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /app/GoAppViewController.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #pragma mark - 4 | #pragma mark GoAppController 5 | 6 | @interface GoAppController : UIViewController 7 | @end 8 | 9 | 10 | #pragma mark - 11 | #pragma mark GoApp 12 | 13 | @interface GoAppView : UIView 14 | @end 15 | -------------------------------------------------------------------------------- /app/GoAppViewController.m: -------------------------------------------------------------------------------- 1 | #import "GoAppViewController.h" 2 | #include "_cgo_export.h" 3 | 4 | #pragma mark - 5 | #pragma mark GoAppController 6 | 7 | @implementation GoAppController { 8 | CADisplayLink* displayLink; 9 | } 10 | 11 | -(void) dealloc { 12 | [displayLink release]; 13 | [super dealloc]; 14 | } 15 | 16 | -(void) viewDidLoad { 17 | [super viewDidLoad]; 18 | 19 | self.view.contentScaleFactor = UIScreen.mainScreen.nativeScale; 20 | 21 | CGSize size = [UIScreen mainScreen].nativeBounds.size; 22 | CGFloat scale = [UIScreen mainScreen].nativeScale; 23 | UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 24 | onConfigurationChanged((int)size.width, (int)size.height, scale, orientation); 25 | 26 | onViewDidLoad((GoUintptr)self.view); 27 | 28 | uint32_t fps = 60; 29 | displayLink = [CADisplayLink displayLinkWithTarget: self selector: @selector(renderLoop)]; 30 | [displayLink setFrameInterval: 60 / fps]; 31 | [displayLink addToRunLoop: NSRunLoop.currentRunLoop forMode: NSDefaultRunLoopMode]; 32 | } 33 | 34 | -(void) renderLoop { 35 | onVSync(); 36 | } 37 | 38 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 39 | [coordinator animateAlongsideTransition: ^ (id context) { 40 | // animate something here 41 | } completion: ^ (id context) { 42 | UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 43 | CGSize size = [UIScreen mainScreen].nativeBounds.size; 44 | CGFloat scale = [UIScreen mainScreen].nativeScale; 45 | onConfigurationChanged((int)size.width, (int)size.height, scale, orientation); 46 | }]; 47 | } 48 | 49 | static void withTouches(int state, NSSet* touches) { 50 | CGFloat scale = [UIScreen mainScreen].scale; 51 | for (UITouch * touch in touches) { 52 | CGPoint p = [touch locationInView:touch.view]; 53 | onTouchEvent((GoUintptr)touch, state, p.x * scale, p.y * scale); 54 | } 55 | } 56 | 57 | - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { 58 | withTouches(0, touches); 59 | } 60 | 61 | - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { 62 | withTouches(1, touches); 63 | } 64 | 65 | - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { 66 | withTouches(2, touches); 67 | } 68 | 69 | - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { 70 | withTouches(3, touches); 71 | } 72 | 73 | @end 74 | 75 | 76 | #pragma mark - 77 | #pragma mark GoAppView 78 | 79 | @implementation GoAppView 80 | 81 | /** Returns a Metal-compatible layer. */ 82 | +(Class) layerClass { return [CAMetalLayer class]; } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /app/handlers.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "C" 4 | import ( 5 | "log" 6 | "time" 7 | ) 8 | 9 | type LifecycleEvent struct { 10 | View uintptr 11 | Kind LifecycleEventKind 12 | } 13 | 14 | type LifecycleEventKind string 15 | 16 | // See also https://developer.apple.com/reference/uikit/uiapplicationdelegate 17 | const ( 18 | ViewDidLoad LifecycleEventKind = "viewDidLoad" 19 | 20 | // WillResignActive is sent when the application is about to move 21 | // from active to inactive state. This can occur for certain types of 22 | // temporary interruptions (such as an incoming phone call or SMS message) 23 | // or when the user quits the application and it begins the transition to 24 | // the background state. Use this method to pause ongoing tasks, disable 25 | // timers, and throttle down OpenGL ES frame rates. Games should use this 26 | // method to pause the game. 27 | WillResignActive LifecycleEventKind = "applicationWillResignActive" 28 | // DidEnterBackground signals that you should release shared 29 | // resources, save user data, invalidate timers, and store enough 30 | // application state information to restore your application to its current 31 | // state in case it is terminated later. If your application supports 32 | // background execution, this method is called instead of 33 | // WillTerminate: when the user quits. 34 | DidEnterBackground LifecycleEventKind = "applicationDidEnterBackground" 35 | // WillEnterForeground is sent as part of the transition from the 36 | // background to the inactive state; here you can undo many of the changes 37 | // made on entering the background. 38 | WillEnterForeground LifecycleEventKind = "applicationWillEnterForeground" 39 | // DidBecomeActive should restart any tasks that were paused (or 40 | // not yet started) while the application was inactive. If the application 41 | // was previously in the background, optionally refresh the user interface. 42 | DidBecomeActive LifecycleEventKind = "applicationDidBecomeActive" 43 | // WillTerminate is sent when the application is about to 44 | // terminate. Save data if appropriate. See also 45 | // DidEnterBackground. 46 | WillTerminate LifecycleEventKind = "applicationWillTerminate" 47 | ) 48 | 49 | //export onVSync 50 | func onVSync() { 51 | defaultApp.initWG.Wait() 52 | 53 | select { 54 | case defaultApp.vsyncEvents <- Signal{}: 55 | default: 56 | } 57 | } 58 | 59 | //export onViewDidLoad 60 | func onViewDidLoad(view uintptr) { 61 | defaultApp.initWG.Wait() 62 | 63 | event := LifecycleEvent{ 64 | View: view, 65 | Kind: ViewDidLoad, 66 | } 67 | defaultApp.lifecycleEvents <- event 68 | } 69 | 70 | //export onApplicationWillResignActive 71 | func onApplicationWillResignActive() { 72 | defaultApp.initWG.Wait() 73 | 74 | event := LifecycleEvent{ 75 | Kind: WillResignActive, 76 | } 77 | defaultApp.lifecycleEvents <- event 78 | } 79 | 80 | //export onApplicationDidEnterBackground 81 | func onApplicationDidEnterBackground() { 82 | defaultApp.initWG.Wait() 83 | 84 | event := LifecycleEvent{ 85 | Kind: DidEnterBackground, 86 | } 87 | defaultApp.lifecycleEvents <- event 88 | } 89 | 90 | //export onApplicationWillEnterForeground 91 | func onApplicationWillEnterForeground() { 92 | defaultApp.initWG.Wait() 93 | 94 | event := LifecycleEvent{ 95 | Kind: WillEnterForeground, 96 | } 97 | defaultApp.lifecycleEvents <- event 98 | } 99 | 100 | //export onApplicationDidBecomeActive 101 | func onApplicationDidBecomeActive() { 102 | defaultApp.initWG.Wait() 103 | 104 | event := LifecycleEvent{ 105 | Kind: DidBecomeActive, 106 | } 107 | defaultApp.lifecycleEvents <- event 108 | } 109 | 110 | //export onApplicationWillTerminate 111 | func onApplicationWillTerminate() { 112 | defaultApp.initWG.Wait() 113 | 114 | event := LifecycleEvent{ 115 | Kind: WillTerminate, 116 | } 117 | defaultApp.lifecycleEvents <- event 118 | } 119 | 120 | type Orientation int32 121 | 122 | const ( 123 | OrientationUnknown Orientation = iota 124 | // OrientationPortrait when device oriented vertically, home button on the bottom. 125 | OrientationPortrait 126 | // OrientationPortraitUpsideDown when device oriented vertically, home button on the top. 127 | OrientationPortraitUpsideDown 128 | // OrientationLandscapeLeft when device oriented horizontally, home button on the right. 129 | OrientationLandscapeLeft 130 | // OrientationLandscapeRight when device oriented horizontally, home button on the left. 131 | OrientationLandscapeRight 132 | // OrientationFaceUp when device oriented flat, face up. 133 | OrientationFaceUp 134 | // OrientationFaceDown when device oriented flat, face down. 135 | OrientationFaceDown 136 | ) 137 | 138 | type ConfigurationEvent struct { 139 | NativeWidth int32 140 | NativeHeight int32 141 | NativeScale float32 142 | Orientation Orientation 143 | } 144 | 145 | //export onConfigurationChanged 146 | func onConfigurationChanged(w, h int32, scale float32, orientation int32) { 147 | defaultApp.initWG.Wait() 148 | 149 | out := defaultApp.getConfigurationEventsOut() 150 | if out == nil { 151 | return 152 | } 153 | event := ConfigurationEvent{ 154 | NativeWidth: w, 155 | NativeHeight: h, 156 | NativeScale: scale, 157 | Orientation: Orientation(orientation), 158 | } 159 | select { 160 | case out <- event: 161 | // dispatched 162 | case <-time.After(defaultApp.maxDispatchTime): 163 | // timed out 164 | } 165 | } 166 | 167 | type EventType int32 168 | 169 | const ( 170 | EventTypeTouches EventType = iota 171 | EventTypeMotion 172 | EventTypeRemoteControl 173 | EventTypePresses 174 | ) 175 | 176 | type EventSubtype int32 177 | 178 | const ( 179 | // available in iPhone OS 3.0 180 | EventSubtypeNone EventSubtype = 0 181 | 182 | // for UIEventTypeMotion, available in iPhone OS 3.0 183 | EventSubtypeMotionShake EventSubtype = 1 184 | 185 | // for UIEventTypeRemoteControl, available in iOS 4.0 186 | EventSubtypeRemoteControlPlay EventSubtype = 100 187 | EventSubtypeRemoteControlPause EventSubtype = 101 188 | EventSubtypeRemoteControlStop EventSubtype = 102 189 | EventSubtypeRemoteControlTogglePlayPause EventSubtype = 103 190 | EventSubtypeRemoteControlNextTrack EventSubtype = 104 191 | EventSubtypeRemoteControlPreviousTrack EventSubtype = 105 192 | EventSubtypeRemoteControlBeginSeekingBackward EventSubtype = 106 193 | EventSubtypeRemoteControlEndSeekingBackward EventSubtype = 107 194 | EventSubtypeRemoteControlBeginSeekingForward EventSubtype = 108 195 | EventSubtypeRemoteControlEndSeekingForward EventSubtype = 109 196 | ) 197 | 198 | type TouchesState int32 199 | 200 | const ( 201 | // TouchesBegan is sent when one or more fingers touch down in a view or window. 202 | TouchesBegan TouchesState = iota 203 | // TouchesMoved is sent when one or more fingers associated with an event move within a view or window. 204 | TouchesMoved 205 | // TouchesEnded is sent when one or more fingers are raised from a view or window. 206 | TouchesEnded 207 | // TouchesCancelled is sent when a system event (such as a low-memory warning) cancels a touch event. 208 | TouchesCancelled 209 | ) 210 | 211 | func (state TouchesState) String() string { 212 | switch state { 213 | case TouchesBegan: 214 | return "began" 215 | case TouchesMoved: 216 | return "moved" 217 | case TouchesEnded: 218 | return "ended" 219 | case TouchesCancelled: 220 | return "cancelled" 221 | default: 222 | return "" 223 | } 224 | } 225 | 226 | type MotionState int32 227 | 228 | const ( 229 | // MotionBegan tells that a motion event has begun. 230 | MotionBegan MotionState = iota 231 | // MotionEnded tells that a motion event has ended. 232 | MotionEnded 233 | // MotionCancelled tells that a motion event has been cancelled. 234 | MotionCancelled 235 | ) 236 | 237 | type TouchSequence int32 238 | 239 | type TouchEvent struct { 240 | State TouchesState 241 | Sequence TouchSequence 242 | X, Y float32 243 | } 244 | 245 | //export onTouchEvent 246 | func onTouchEvent(tp uintptr, state int32, x, y float32) { 247 | log.Println("[DEBUG touch]", tp, state, x, y) 248 | defaultApp.initWG.Wait() 249 | out := defaultApp.getTouchEventsOut() 250 | if out == nil { 251 | return 252 | } 253 | 254 | seq := -1 255 | for i, val := range touchIDs { 256 | if val == tp { 257 | seq = i 258 | break 259 | } 260 | } 261 | if seq == -1 { 262 | for i, val := range touchIDs { 263 | if val == 0 { 264 | touchIDs[i] = tp 265 | seq = i 266 | break 267 | } 268 | } 269 | if seq == -1 { 270 | panic("maximum touch sequence length exceeded") 271 | } 272 | } 273 | 274 | s := TouchesState(state) 275 | if s == TouchesEnded || s == TouchesCancelled { 276 | touchIDs[seq] = 0 277 | } 278 | event := TouchEvent{ 279 | X: x, 280 | Y: y, 281 | State: s, 282 | Sequence: TouchSequence(seq), 283 | } 284 | select { 285 | case out <- event: 286 | // dispatched 287 | case <-time.After(defaultApp.maxDispatchTime): 288 | // timed out 289 | } 290 | } 291 | 292 | // touchIDs is the current active touches. The position in the array 293 | // is the ID, the value is the UITouch* pointer value. 294 | var touchIDs [11]uintptr 295 | -------------------------------------------------------------------------------- /app/main.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | /* 4 | #cgo CFLAGS: -x objective-c 5 | #cgo LDFLAGS: -framework Foundation -framework UIKit -framework QuartzCore 6 | 7 | #include 8 | 9 | void runApp(void); 10 | */ 11 | import "C" 12 | import "runtime" 13 | 14 | func init() { 15 | // Lock the goroutine responsible for initialization to an OS thread. 16 | // This means the goroutine running main (and calling the run function 17 | // below) is locked to the OS thread that started the program. This is 18 | // necessary for the correct delivery of UIKit events to the process. 19 | // 20 | // A discussion on this topic: 21 | // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ 22 | runtime.LockOSThread() 23 | 24 | defaultApp.initWG.Add(1) 25 | } 26 | 27 | func Main(f func(AppDelegate)) { 28 | defer runtime.UnlockOSThread() 29 | 30 | go f(defaultApp) // run in a separate thread 31 | C.runApp() // remains bound to the OS thread 32 | panic("runApp unexpected exit") 33 | } 34 | -------------------------------------------------------------------------------- /app/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | void runApp(void) { 4 | @autoreleasepool { 5 | UIApplicationMain(0, nil, nil, @"AppDelegate"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build/ 3 | main/main 4 | main.xcodeproj/xcuserdata 5 | main.xcodeproj/project.xcworkspace 6 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | WORK ?= $(shell pwd) 2 | PACKAGE ?= github.com/xlab/ios-go/example 3 | 4 | CC ?= $(shell xcrun --sdk iphoneos --find clang) 5 | CXX ?= $(shell xcrun --sdk iphoneos --find clang) 6 | ISYSROOT ?= $(shell xcrun --sdk iphoneos --show-sdk-path) 7 | 8 | all: build 9 | 10 | build: 11 | mkdir -p $(WORK)/build 12 | GOOS=darwin GOARCH=arm GOARM=7 CC=$(CC) CXX=$(CXX) CGO_CFLAGS="-isysroot $(ISYSROOT) -miphoneos-version-min=8.0 -arch armv7" CGO_LDFLAGS="-isysroot $(ISYSROOT) -miphoneos-version-min=8.0 -arch armv7" CGO_ENABLED=1 go build -tags="ios" -o=$(WORK)/build/arm $(PACKAGE) 13 | GOOS=darwin GOARCH=arm64 CC=$(CC) CXX=$(CXX) CGO_CFLAGS="-isysroot $(ISYSROOT) -miphoneos-version-min=8.0 -arch arm64" CGO_LDFLAGS="-isysroot $(ISYSROOT) -miphoneos-version-min=8.0 -arch arm64" CGO_ENABLED=1 go build -tags="ios" -o=$(WORK)/build/arm64 $(PACKAGE) 14 | xcrun lipo -create $(WORK)/build/arm $(WORK)/build/arm64 -o $(WORK)/main/main 15 | xcrun xcodebuild -configuration Release -project $(WORK)/main.xcodeproj 16 | 17 | install: 18 | # https://github.com/phonegap/ios-deploy 19 | ios-deploy -b $(WORK)/build/Release-iphoneos/GoAppExample.app 20 | 21 | debug: 22 | ios-deploy -d -W -b $(WORK)/build/Release-iphoneos/GoAppExample.app 23 | 24 | clean: 25 | rm -rf $(WORK)/build 26 | rm -f $(WORK)/main/main 27 | 28 | .PHONY: build -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/xlab/ios-go/app" 7 | ) 8 | 9 | func main() { 10 | log.Println("GoApp has started ^_^") 11 | configEvents := make(chan app.ConfigurationEvent, 1) 12 | touchEvents := make(chan app.TouchEvent, 10) 13 | 14 | app.Main(func(a app.AppDelegate) { 15 | a.HandleConfigurationEvents(configEvents) 16 | a.HandleTouchEvents(touchEvents) 17 | a.InitDone() 18 | for { 19 | select { 20 | case event := <-a.LifecycleEvents(): 21 | switch event.Kind { 22 | case app.ViewDidLoad: 23 | log.Println(event.Kind, "handled") 24 | default: 25 | log.Println(event.Kind, "event ignored") 26 | } 27 | case cfg := <-configEvents: 28 | log.Printf("rotated device: %+v\n", cfg) 29 | case tc := <-touchEvents: 30 | log.Printf("touch[%d]: (%.1f,%.1f) -> %s\n", tc.Sequence, tc.X, tc.Y, tc.State) 31 | case <-a.VSync(): 32 | // no-op 33 | } 34 | } 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /example/main.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 254BB84E1B1FD08900C56DE9 /* Images.xcassets */; }; 11 | 254BB8681B1FD16500C56DE9 /* main in Resources */ = {isa = PBXBuildFile; fileRef = 254BB8671B1FD16500C56DE9 /* main */; }; 12 | 25FB30331B30FDEE0005924C /* assets in Resources */ = {isa = PBXBuildFile; fileRef = 25FB30321B30FDEE0005924C /* assets */; }; 13 | F67AD8751DD5392F002CF957 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F67AD8741DD5392F002CF957 /* Main.storyboard */; }; 14 | F6B3A5B11DD53D7F006D3B37 /* main.go in Resources */ = {isa = PBXBuildFile; fileRef = F6B3A5B01DD53D7F006D3B37 /* main.go */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXFileReference section */ 18 | 254BB83E1B1FD08900C56DE9 /* main.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; name = main.app; path = GoAppExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 19 | 254BB8421B1FD08900C56DE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 20 | 254BB84E1B1FD08900C56DE9 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 21 | 254BB8671B1FD16500C56DE9 /* main */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; path = main; sourceTree = ""; }; 22 | 25FB30321B30FDEE0005924C /* assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = assets; path = main/assets; sourceTree = ""; }; 23 | F67AD8741DD5392F002CF957 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 24 | F6B3A5B01DD53D7F006D3B37 /* main.go */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.go; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXGroup section */ 28 | 254BB8351B1FD08900C56DE9 = { 29 | isa = PBXGroup; 30 | children = ( 31 | F6B3A5B01DD53D7F006D3B37 /* main.go */, 32 | 25FB30321B30FDEE0005924C /* assets */, 33 | 254BB8401B1FD08900C56DE9 /* main */, 34 | 254BB83F1B1FD08900C56DE9 /* Products */, 35 | ); 36 | sourceTree = ""; 37 | usesTabs = 0; 38 | }; 39 | 254BB83F1B1FD08900C56DE9 /* Products */ = { 40 | isa = PBXGroup; 41 | children = ( 42 | 254BB83E1B1FD08900C56DE9 /* main.app */, 43 | ); 44 | name = Products; 45 | sourceTree = ""; 46 | }; 47 | 254BB8401B1FD08900C56DE9 /* main */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 254BB8671B1FD16500C56DE9 /* main */, 51 | F67AD8741DD5392F002CF957 /* Main.storyboard */, 52 | 254BB84E1B1FD08900C56DE9 /* Images.xcassets */, 53 | 254BB8411B1FD08900C56DE9 /* Supporting Files */, 54 | ); 55 | path = main; 56 | sourceTree = ""; 57 | }; 58 | 254BB8411B1FD08900C56DE9 /* Supporting Files */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 254BB8421B1FD08900C56DE9 /* Info.plist */, 62 | ); 63 | name = "Supporting Files"; 64 | sourceTree = ""; 65 | }; 66 | /* End PBXGroup section */ 67 | 68 | /* Begin PBXNativeTarget section */ 69 | 254BB83D1B1FD08900C56DE9 /* main */ = { 70 | isa = PBXNativeTarget; 71 | buildConfigurationList = 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */; 72 | buildPhases = ( 73 | 254BB83C1B1FD08900C56DE9 /* Resources */, 74 | ); 75 | buildRules = ( 76 | ); 77 | dependencies = ( 78 | ); 79 | name = main; 80 | productName = main; 81 | productReference = 254BB83E1B1FD08900C56DE9 /* main.app */; 82 | productType = "com.apple.product-type.application"; 83 | }; 84 | /* End PBXNativeTarget section */ 85 | 86 | /* Begin PBXProject section */ 87 | 254BB8361B1FD08900C56DE9 /* Project object */ = { 88 | isa = PBXProject; 89 | attributes = { 90 | LastUpgradeCheck = 0810; 91 | ORGANIZATIONNAME = Developer; 92 | TargetAttributes = { 93 | 254BB83D1B1FD08900C56DE9 = { 94 | CreatedOnToolsVersion = 6.3.1; 95 | DevelopmentTeam = 37KKRKX364; 96 | }; 97 | }; 98 | }; 99 | buildConfigurationList = 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */; 100 | compatibilityVersion = "Xcode 3.2"; 101 | developmentRegion = English; 102 | hasScannedForEncodings = 0; 103 | knownRegions = ( 104 | en, 105 | Base, 106 | ); 107 | mainGroup = 254BB8351B1FD08900C56DE9; 108 | productRefGroup = 254BB83F1B1FD08900C56DE9 /* Products */; 109 | projectDirPath = ""; 110 | projectRoot = ""; 111 | targets = ( 112 | 254BB83D1B1FD08900C56DE9 /* main */, 113 | ); 114 | }; 115 | /* End PBXProject section */ 116 | 117 | /* Begin PBXResourcesBuildPhase section */ 118 | 254BB83C1B1FD08900C56DE9 /* Resources */ = { 119 | isa = PBXResourcesBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 25FB30331B30FDEE0005924C /* assets in Resources */, 123 | 254BB8681B1FD16500C56DE9 /* main in Resources */, 124 | 254BB84F1B1FD08900C56DE9 /* Images.xcassets in Resources */, 125 | F67AD8751DD5392F002CF957 /* Main.storyboard in Resources */, 126 | F6B3A5B11DD53D7F006D3B37 /* main.go in Resources */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXResourcesBuildPhase section */ 131 | 132 | /* Begin XCBuildConfiguration section */ 133 | 254BB8601B1FD08900C56DE9 /* Release */ = { 134 | isa = XCBuildConfiguration; 135 | buildSettings = { 136 | ALWAYS_SEARCH_USER_PATHS = NO; 137 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_WARN_BOOL_CONVERSION = YES; 142 | CLANG_WARN_CONSTANT_CONVERSION = YES; 143 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 144 | CLANG_WARN_EMPTY_BODY = YES; 145 | CLANG_WARN_ENUM_CONVERSION = YES; 146 | CLANG_WARN_INFINITE_RECURSION = YES; 147 | CLANG_WARN_INT_CONVERSION = YES; 148 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 149 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 150 | CLANG_WARN_UNREACHABLE_CODE = YES; 151 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 152 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 153 | COPY_PHASE_STRIP = NO; 154 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 155 | DEVELOPMENT_TEAM = 37KKRKX364; 156 | ENABLE_NS_ASSERTIONS = NO; 157 | ENABLE_STRICT_OBJC_MSGSEND = YES; 158 | GCC_C_LANGUAGE_STANDARD = gnu99; 159 | GCC_NO_COMMON_BLOCKS = YES; 160 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 161 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 162 | GCC_WARN_UNDECLARED_SELECTOR = YES; 163 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 164 | GCC_WARN_UNUSED_FUNCTION = YES; 165 | GCC_WARN_UNUSED_VARIABLE = YES; 166 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 167 | MTL_ENABLE_DEBUG_INFO = NO; 168 | SDKROOT = iphoneos; 169 | TARGETED_DEVICE_FAMILY = "1,2"; 170 | VALIDATE_PRODUCT = YES; 171 | }; 172 | name = Release; 173 | }; 174 | 254BB8631B1FD08900C56DE9 /* Release */ = { 175 | isa = XCBuildConfiguration; 176 | buildSettings = { 177 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 178 | INFOPLIST_FILE = main/Info.plist; 179 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 180 | PRODUCT_BUNDLE_IDENTIFIER = org.golang.ios.example; 181 | PRODUCT_NAME = GoAppExample; 182 | }; 183 | name = Release; 184 | }; 185 | /* End XCBuildConfiguration section */ 186 | 187 | /* Begin XCConfigurationList section */ 188 | 254BB8391B1FD08900C56DE9 /* Build configuration list for PBXProject "main" */ = { 189 | isa = XCConfigurationList; 190 | buildConfigurations = ( 191 | 254BB8601B1FD08900C56DE9 /* Release */, 192 | ); 193 | defaultConfigurationIsVisible = 0; 194 | defaultConfigurationName = Release; 195 | }; 196 | 254BB8611B1FD08900C56DE9 /* Build configuration list for PBXNativeTarget "main" */ = { 197 | isa = XCConfigurationList; 198 | buildConfigurations = ( 199 | 254BB8631B1FD08900C56DE9 /* Release */, 200 | ); 201 | defaultConfigurationIsVisible = 0; 202 | defaultConfigurationName = Release; 203 | }; 204 | /* End XCConfigurationList section */ 205 | }; 206 | rootObject = 254BB8361B1FD08900C56DE9 /* Project object */; 207 | } 208 | -------------------------------------------------------------------------------- /example/main/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | } 88 | ], 89 | "info" : { 90 | "version" : 1, 91 | "author" : "xcode" 92 | } 93 | } -------------------------------------------------------------------------------- /example/main/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | main 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/main/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/main/assets/.gitnoignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xlab/ios-go/5c6163f67cd44df63ae64300959b362f7e2b3ed9/example/main/assets/.gitnoignore --------------------------------------------------------------------------------