├── 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 [](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
--------------------------------------------------------------------------------