summaryrefslogtreecommitdiff
path: root/src/ui/ios/gf-ios-swift
diff options
context:
space:
mode:
authorjoel.hinz <joel.hinz@gmail.com>2015-02-20 07:47:43 +0000
committerjoel.hinz <joel.hinz@gmail.com>2015-02-20 07:47:43 +0000
commit2394763daaedab05fe3fa35635317dfaca7092bf (patch)
treeb3de50a3af5d344f74fb0545ce9931b285277166 /src/ui/ios/gf-ios-swift
parentf848857519bfb093310503108ff62297ea9f8a24 (diff)
translation app for iOS, replicating some of the functionality of the Android app. Compiles and works on iPad retina but may crash and has known issues.
Diffstat (limited to 'src/ui/ios/gf-ios-swift')
-rw-r--r--src/ui/ios/gf-ios-swift/AppDelegate.swift38
-rw-r--r--src/ui/ios/gf-ios-swift/Base.lproj/LaunchScreen.xib32
-rw-r--r--src/ui/ios/gf-ios-swift/Base.lproj/Main.storyboard242
-rw-r--r--src/ui/ios/gf-ios-swift/Colors.swift72
-rw-r--r--src/ui/ios/gf-ios-swift/HelpViewController.swift24
-rw-r--r--src/ui/ios/gf-ios-swift/Images.xcassets/AppIcon.appiconset/Contents.json68
-rw-r--r--src/ui/ios/gf-ios-swift/Info.plist47
-rw-r--r--src/ui/ios/gf-ios-swift/Languages.swift39
-rw-r--r--src/ui/ios/gf-ios-swift/Translator.swift210
-rw-r--r--src/ui/ios/gf-ios-swift/ViewController.swift223
-rw-r--r--src/ui/ios/gf-ios-swift/gf-ios-swift-Bridging-Header.h13
-rw-r--r--src/ui/ios/gf-ios-swift/images/ic_action_switch.pngbin0 -> 436 bytes
-rw-r--r--src/ui/ios/gf-ios-swift/images/ic_app copy.pngbin0 -> 2458 bytes
-rw-r--r--src/ui/ios/gf-ios-swift/images/ic_app.pngbin0 -> 2073 bytes
-rw-r--r--src/ui/ios/gf-ios-swift/images/ic_keyboard.pngbin0 -> 695 bytes
-rw-r--r--src/ui/ios/gf-ios-swift/images/ic_mic.pngbin0 -> 665 bytes
16 files changed, 1008 insertions, 0 deletions
diff --git a/src/ui/ios/gf-ios-swift/AppDelegate.swift b/src/ui/ios/gf-ios-swift/AppDelegate.swift
new file mode 100644
index 000000000..f70ff9aa2
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/AppDelegate.swift
@@ -0,0 +1,38 @@
+import UIKit
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ var window: UIWindow?
+
+
+ func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
+ // Override point for customization after application launch.
+ return true
+ }
+
+ func applicationWillResignActive(application: UIApplication) {
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+ }
+
+ func applicationDidEnterBackground(application: UIApplication) {
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+ }
+
+ func applicationWillEnterForeground(application: UIApplication) {
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+ }
+
+ func applicationDidBecomeActive(application: UIApplication) {
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+ }
+
+ func applicationWillTerminate(application: UIApplication) {
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+ }
+
+
+}
+
diff --git a/src/ui/ios/gf-ios-swift/Base.lproj/LaunchScreen.xib b/src/ui/ios/gf-ios-swift/Base.lproj/LaunchScreen.xib
new file mode 100644
index 000000000..08d58bfed
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Base.lproj/LaunchScreen.xib
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
+ <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
+ </dependencies>
+ <objects>
+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
+ <view contentMode="scaleToFill" id="iN0-l3-epB">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="gf-ios-swift" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
+ <rect key="frame" x="20" y="140" width="441" height="43"/>
+ <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
+ <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
+ <constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
+ </constraints>
+ <nil key="simulatedStatusBarMetrics"/>
+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
+ <point key="canvasLocation" x="548" y="455"/>
+ </view>
+ </objects>
+</document>
diff --git a/src/ui/ios/gf-ios-swift/Base.lproj/Main.storyboard b/src/ui/ios/gf-ios-swift/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..ff087bd67
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Base.lproj/Main.storyboard
@@ -0,0 +1,242 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="6254" systemVersion="14B25" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+ <dependencies>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
+ <capability name="Alignment constraints to the first baseline" minToolsVersion="6.0"/>
+ <capability name="Alignment constraints with different attributes" minToolsVersion="5.1"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ </dependencies>
+ <scenes>
+ <!--Main view controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController title="Main view controller" id="BYZ-38-t0r" customClass="ViewController" customModule="gf_ios_swift" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="l7I-EE-PVz"/>
+ <viewControllerLayoutGuide type="bottom" id="2id-YU-Gsq"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="wae-JL-Lho">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <subviews>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jLE-dy-saG">
+ <rect key="frame" x="0.0" y="20" width="600" height="54"/>
+ <subviews>
+ <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="ic_app.png" translatesAutoresizingMaskIntoConstraints="NO" id="uee-Kw-8PE">
+ <rect key="frame" x="8" y="9" width="36" height="36"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="GF Offline Translator" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mPU-2n-FFA">
+ <rect key="frame" x="52" y="16" width="157" height="21"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="3n3-Sf-P9J">
+ <rect key="frame" x="557" y="12" width="35" height="30"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="35" id="qUL-Aa-ctK"/>
+ </constraints>
+ <state key="normal" title="Help">
+ <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <segue destination="hV5-UM-8hP" kind="show" id="Wbx-xn-VFi"/>
+ </connections>
+ </button>
+ </subviews>
+ <color key="backgroundColor" red="0.2265625" green="0.2265625" blue="0.2265625" alpha="1" colorSpace="calibratedRGB"/>
+ <constraints>
+ <constraint firstAttribute="bottom" secondItem="mPU-2n-FFA" secondAttribute="bottom" constant="17" id="1y1-Dl-MV6"/>
+ <constraint firstItem="mPU-2n-FFA" firstAttribute="baseline" secondItem="3n3-Sf-P9J" secondAttribute="baseline" id="5g0-IW-7M5"/>
+ <constraint firstItem="uee-Kw-8PE" firstAttribute="leading" secondItem="jLE-dy-saG" secondAttribute="leadingMargin" id="CqM-xw-M6e"/>
+ <constraint firstItem="uee-Kw-8PE" firstAttribute="centerY" secondItem="3n3-Sf-P9J" secondAttribute="centerY" id="J2z-8G-1d5"/>
+ <constraint firstItem="mPU-2n-FFA" firstAttribute="leading" secondItem="uee-Kw-8PE" secondAttribute="trailing" constant="8" symbolic="YES" id="N02-lW-stz"/>
+ <constraint firstItem="3n3-Sf-P9J" firstAttribute="trailing" secondItem="jLE-dy-saG" secondAttribute="trailingMargin" id="Ydq-87-ZdY"/>
+ <constraint firstItem="mPU-2n-FFA" firstAttribute="top" secondItem="jLE-dy-saG" secondAttribute="top" constant="16" id="afL-yS-6ju"/>
+ <constraint firstItem="mPU-2n-FFA" firstAttribute="baseline" secondItem="3n3-Sf-P9J" secondAttribute="firstBaseline" id="ffK-JY-sh8"/>
+ <constraint firstItem="uee-Kw-8PE" firstAttribute="centerY" secondItem="jLE-dy-saG" secondAttribute="centerY" id="h0F-TI-Xbk"/>
+ </constraints>
+ </view>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wH0-0y-PJl">
+ <rect key="frame" x="0.0" y="74" width="600" height="90"/>
+ <subviews>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="English" borderStyle="roundedRect" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="g1E-VP-f78">
+ <rect key="frame" x="8" y="8" width="180" height="30"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="180" id="KtT-7A-FcQ"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Swedish" borderStyle="roundedRect" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="31S-0q-S0M">
+ <rect key="frame" x="8" y="52" width="180" height="30"/>
+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
+ <textInputTraits key="textInputTraits" enablesReturnKeyAutomatically="YES"/>
+ </textField>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q8W-YQ-yVf">
+ <rect key="frame" x="546" y="22" width="46" height="46"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="46" id="wA6-fY-6Yn"/>
+ </constraints>
+ <state key="normal" backgroundImage="ic_mic.png">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="openMicrophone:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Sxu-s0-KiU"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Fcg-Gz-VL8">
+ <rect key="frame" x="492" y="12" width="46" height="46"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="46" id="2Iu-eu-EEi"/>
+ </constraints>
+ <state key="normal" backgroundImage="ic_keyboard.png">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="openKeyboard:" destination="BYZ-38-t0r" eventType="touchUpInside" id="mXc-7v-Gym"/>
+ </connections>
+ </button>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lZ6-Um-4nv">
+ <rect key="frame" x="438" y="22" width="46" height="46"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="46" id="skb-99-Z93"/>
+ </constraints>
+ <state key="normal" backgroundImage="ic_action_switch.png">
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="switchLanguages:" destination="BYZ-38-t0r" eventType="touchUpInside" id="gxh-Mn-swH"/>
+ </connections>
+ </button>
+ </subviews>
+ <color key="backgroundColor" red="0.83405991020000003" green="0.83405991020000003" blue="0.83405991020000003" alpha="1" colorSpace="calibratedRGB"/>
+ <constraints>
+ <constraint firstItem="g1E-VP-f78" firstAttribute="leading" secondItem="31S-0q-S0M" secondAttribute="leading" id="Axm-A7-AW5"/>
+ <constraint firstItem="31S-0q-S0M" firstAttribute="bottom" secondItem="wH0-0y-PJl" secondAttribute="bottomMargin" id="D3J-OI-cOp"/>
+ <constraint firstItem="g1E-VP-f78" firstAttribute="leading" secondItem="wH0-0y-PJl" secondAttribute="leadingMargin" id="Gb3-Nd-JYg"/>
+ <constraint firstItem="Fcg-Gz-VL8" firstAttribute="leading" secondItem="lZ6-Um-4nv" secondAttribute="trailing" constant="8" symbolic="YES" id="H09-XK-pR5"/>
+ <constraint firstAttribute="bottom" secondItem="Fcg-Gz-VL8" secondAttribute="bottom" constant="32" id="Jdk-k5-zmU"/>
+ <constraint firstItem="lZ6-Um-4nv" firstAttribute="baseline" secondItem="q8W-YQ-yVf" secondAttribute="baseline" id="R1m-B4-uTV"/>
+ <constraint firstItem="q8W-YQ-yVf" firstAttribute="trailing" secondItem="wH0-0y-PJl" secondAttribute="trailingMargin" id="SoL-3l-uQ3"/>
+ <constraint firstItem="q8W-YQ-yVf" firstAttribute="firstBaseline" secondItem="lZ6-Um-4nv" secondAttribute="firstBaseline" id="T7u-nU-sjg"/>
+ <constraint firstItem="g1E-VP-f78" firstAttribute="top" secondItem="wH0-0y-PJl" secondAttribute="topMargin" id="Why-mX-52K"/>
+ <constraint firstItem="Fcg-Gz-VL8" firstAttribute="top" secondItem="wH0-0y-PJl" secondAttribute="top" constant="12" id="ahZ-rS-wDW"/>
+ <constraint firstItem="lZ6-Um-4nv" firstAttribute="top" secondItem="q8W-YQ-yVf" secondAttribute="top" id="eGp-Dy-lng"/>
+ <constraint firstItem="lZ6-Um-4nv" firstAttribute="top" secondItem="wH0-0y-PJl" secondAttribute="top" constant="22" id="flR-i8-Tx1"/>
+ <constraint firstItem="q8W-YQ-yVf" firstAttribute="bottom" secondItem="lZ6-Um-4nv" secondAttribute="bottom" id="iqb-lC-ww8"/>
+ <constraint firstItem="q8W-YQ-yVf" firstAttribute="centerY" secondItem="wH0-0y-PJl" secondAttribute="centerY" id="mjJ-gt-CuI"/>
+ <constraint firstItem="g1E-VP-f78" firstAttribute="trailing" secondItem="31S-0q-S0M" secondAttribute="trailing" id="oUn-Pm-i8j"/>
+ <constraint firstItem="q8W-YQ-yVf" firstAttribute="leading" secondItem="Fcg-Gz-VL8" secondAttribute="trailing" constant="8" symbolic="YES" id="xwg-yQ-qaP"/>
+ </constraints>
+ </view>
+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="g83-TI-wJI">
+ <rect key="frame" x="0.0" y="172" width="600" height="428"/>
+ </scrollView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="g83-TI-wJI" firstAttribute="top" secondItem="wae-JL-Lho" secondAttribute="top" constant="180" id="0cO-n9-ttA"/>
+ <constraint firstItem="wH0-0y-PJl" firstAttribute="trailing" secondItem="jLE-dy-saG" secondAttribute="trailing" id="DXh-P8-F7d"/>
+ <constraint firstItem="jLE-dy-saG" firstAttribute="leading" secondItem="wH0-0y-PJl" secondAttribute="leading" id="Di9-vy-kd0"/>
+ <constraint firstItem="wH0-0y-PJl" firstAttribute="top" secondItem="jLE-dy-saG" secondAttribute="bottom" id="FNh-FO-Vt1"/>
+ <constraint firstItem="wH0-0y-PJl" firstAttribute="leading" secondItem="g83-TI-wJI" secondAttribute="leading" id="J9k-62-AZR"/>
+ <constraint firstItem="wH0-0y-PJl" firstAttribute="trailing" secondItem="g83-TI-wJI" secondAttribute="trailing" id="JwC-oH-gH3"/>
+ <constraint firstItem="g83-TI-wJI" firstAttribute="bottom" secondItem="2id-YU-Gsq" secondAttribute="top" id="NeO-Dx-ijg"/>
+ <constraint firstItem="wH0-0y-PJl" firstAttribute="leading" secondItem="wae-JL-Lho" secondAttribute="leading" id="Tdu-cl-vkb"/>
+ <constraint firstItem="jLE-dy-saG" firstAttribute="top" secondItem="l7I-EE-PVz" secondAttribute="bottom" id="l44-L7-DvW"/>
+ <constraint firstItem="g83-TI-wJI" firstAttribute="top" secondItem="wH0-0y-PJl" secondAttribute="bottom" constant="16" id="qEh-li-kCJ"/>
+ <constraint firstAttribute="trailing" secondItem="wH0-0y-PJl" secondAttribute="trailing" id="z8B-qQ-x9h"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="inLanguageField" destination="g1E-VP-f78" id="mYE-F2-WDr"/>
+ <outlet property="outLanguageField" destination="31S-0q-S0M" id="MtL-xd-MTp"/>
+ <outlet property="translationsView" destination="g83-TI-wJI" id="BQG-2F-M7R"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="441" y="260"/>
+ </scene>
+ <!--Help View Controller-->
+ <scene sceneID="B4j-Du-kfE">
+ <objects>
+ <viewController id="hV5-UM-8hP" customClass="HelpViewController" customModule="gf_ios_swift" customModuleProvider="target" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="R7u-gU-P1Q"/>
+ <viewControllerLayoutGuide type="bottom" id="Rse-F6-hEW"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="bOe-jW-W6L">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+ <subviews>
+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Uq7-9l-AXI">
+ <rect key="frame" x="0.0" y="20" width="600" height="54"/>
+ <subviews>
+ <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="ic_app.png" translatesAutoresizingMaskIntoConstraints="NO" id="FGK-rl-cxy">
+ <rect key="frame" x="8" y="9" width="36" height="36"/>
+ </imageView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="GF Offline Translator" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fhs-5e-tT6">
+ <rect key="frame" x="52" y="16" width="157" height="21"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rcA-bk-D0U">
+ <rect key="frame" x="557" y="12" width="35" height="30"/>
+ <state key="normal" title="Back">
+ <color key="titleColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <color key="titleShadowColor" white="0.5" alpha="1" colorSpace="calibratedWhite"/>
+ </state>
+ <connections>
+ <action selector="backToMain:" destination="hV5-UM-8hP" eventType="touchUpInside" id="gbz-vX-zeX"/>
+ </connections>
+ </button>
+ </subviews>
+ <color key="backgroundColor" red="0.2265625" green="0.2265625" blue="0.2265625" alpha="1" colorSpace="calibratedRGB"/>
+ <constraints>
+ <constraint firstItem="FGK-rl-cxy" firstAttribute="centerY" secondItem="Uq7-9l-AXI" secondAttribute="centerY" id="8jw-J3-DrQ"/>
+ <constraint firstItem="Fhs-5e-tT6" firstAttribute="top" secondItem="Uq7-9l-AXI" secondAttribute="top" constant="16" id="H3f-Ty-IXp"/>
+ <constraint firstItem="Fhs-5e-tT6" firstAttribute="leading" secondItem="FGK-rl-cxy" secondAttribute="trailing" constant="8" symbolic="YES" id="QiZ-YR-hv3"/>
+ <constraint firstItem="FGK-rl-cxy" firstAttribute="centerY" secondItem="rcA-bk-D0U" secondAttribute="centerY" id="WhQ-TP-XF6"/>
+ <constraint firstItem="rcA-bk-D0U" firstAttribute="trailing" secondItem="Uq7-9l-AXI" secondAttribute="trailingMargin" id="nK2-Dy-kAb"/>
+ <constraint firstItem="Fhs-5e-tT6" firstAttribute="baseline" secondItem="rcA-bk-D0U" secondAttribute="firstBaseline" id="nPm-M8-phv"/>
+ <constraint firstItem="rcA-bk-D0U" firstAttribute="baseline" secondItem="Fhs-5e-tT6" secondAttribute="baseline" id="rMp-YH-284"/>
+ <constraint firstItem="FGK-rl-cxy" firstAttribute="leading" secondItem="Uq7-9l-AXI" secondAttribute="leadingMargin" id="yKh-eI-L5b"/>
+ <constraint firstItem="FGK-rl-cxy" firstAttribute="top" secondItem="Uq7-9l-AXI" secondAttribute="top" constant="9" id="ynj-zV-Yd5"/>
+ </constraints>
+ </view>
+ <webView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bgm-Yn-C8w">
+ <rect key="frame" x="0.0" y="74" width="600" height="526"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ </webView>
+ </subviews>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
+ <constraints>
+ <constraint firstItem="Uq7-9l-AXI" firstAttribute="leading" secondItem="bOe-jW-W6L" secondAttribute="leading" id="Mom-vU-jac"/>
+ <constraint firstItem="bgm-Yn-C8w" firstAttribute="trailing" secondItem="Uq7-9l-AXI" secondAttribute="trailing" id="fc7-8r-ipb"/>
+ <constraint firstAttribute="trailing" secondItem="Uq7-9l-AXI" secondAttribute="trailing" id="pg0-uu-87C"/>
+ <constraint firstItem="bgm-Yn-C8w" firstAttribute="bottom" secondItem="Rse-F6-hEW" secondAttribute="top" id="ske-sI-SW3"/>
+ <constraint firstItem="bgm-Yn-C8w" firstAttribute="top" secondItem="Uq7-9l-AXI" secondAttribute="bottom" id="tLc-we-gBp"/>
+ <constraint firstItem="bgm-Yn-C8w" firstAttribute="leading" secondItem="Uq7-9l-AXI" secondAttribute="leading" id="wvo-y4-aJm"/>
+ <constraint firstItem="Uq7-9l-AXI" firstAttribute="top" secondItem="bOe-jW-W6L" secondAttribute="top" constant="20" id="x73-hi-CDW"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="helpView" destination="bgm-Yn-C8w" id="cm9-Jg-J95"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="qSQ-Y9-ECH" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="1056" y="260"/>
+ </scene>
+ </scenes>
+ <resources>
+ <image name="ic_action_switch.png" width="48" height="48"/>
+ <image name="ic_app.png" width="36" height="36"/>
+ <image name="ic_keyboard.png" width="48" height="48"/>
+ <image name="ic_mic.png" width="48" height="48"/>
+ </resources>
+</document>
diff --git a/src/ui/ios/gf-ios-swift/Colors.swift b/src/ui/ios/gf-ios-swift/Colors.swift
new file mode 100644
index 000000000..960d1bd35
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Colors.swift
@@ -0,0 +1,72 @@
+import UIKit
+import Foundation
+
+// An extension to UIColor which allows the use of hex initialisation
+extension UIColor {
+
+ convenience init(hex: Int) {
+
+ let components = (
+ red: CGFloat((hex >> 16) & 0xff) / 255,
+ green: CGFloat((hex >> 08) & 0xff) / 255,
+ blue: CGFloat((hex >> 00) & 0xff) / 255
+ )
+
+ self.init(red: components.red, green: components.green, blue: components.blue, alpha: 1.0)
+
+ }
+
+}
+
+class Colors {
+
+ // Set the background colours to use by the view controller
+ let backgroundColors = [
+ "worst": UIColor(hex: 0xff303e),
+ "best": UIColor(hex: 0x75cd75),
+ "chunks": UIColor(hex: 0xffb2a5),
+ "default": UIColor(hex: 0xffff99),
+ "source": UIColor(hex: 0xcdcded)
+ ]
+
+ // Checks a given translated string to see how good it is and hence which background colour it should have
+ func translationToUIColor(translation: String) -> UIColor {
+
+ if (translation.isEmpty) {
+ return self.backgroundColors["default"]!
+ }
+
+ var firstChar = String(Array(translation)[0])
+
+ // Parse by words, marked by %, darkest red color
+ if (firstChar == "%") {
+ return self.backgroundColors["worst"]!
+ }
+
+ // Parse by chunks, marked by *, red color
+ if (firstChar == "*") {
+ return self.backgroundColors["chunks"]!
+ }
+
+ // Parse error: darkest red color
+ if (translation.rangeOfString("parse error:") != nil) {
+ return self.backgroundColors["worst"]!
+ }
+
+ // Unknown linearizations in output
+ if (translation.rangeOfString("[") != nil) {
+ return self.backgroundColors["worst"]!
+ }
+
+ // Parse by domain grammar, marked by +, green color
+ if (firstChar == "+") {
+ return self.backgroundColors["best"]!
+ }
+
+ // Otherwise, use the default colour
+ return self.backgroundColors["default"]!
+
+ }
+
+
+} \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/HelpViewController.swift b/src/ui/ios/gf-ios-swift/HelpViewController.swift
new file mode 100644
index 000000000..7f924bcd4
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/HelpViewController.swift
@@ -0,0 +1,24 @@
+import UIKit
+
+// The HelpViewController only contains static text and a back button
+class HelpViewController: UIViewController {
+
+ @IBOutlet weak var helpView: UIWebView!
+
+ // Closes the view when the back button is blicked
+ @IBAction func backToMain(sender: UIButton) {
+ dismissViewControllerAnimated(true, completion: nil)
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Sets the HTML for the UIWebView
+ self.helpView.loadHTMLString("<html><body><h1>HTML goes here</h1></body></html>", baseURL: nil)
+ }
+
+ override func didReceiveMemoryWarning() {
+ super.didReceiveMemoryWarning()
+ }
+
+}
diff --git a/src/ui/ios/gf-ios-swift/Images.xcassets/AppIcon.appiconset/Contents.json b/src/ui/ios/gf-ios-swift/Images.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..36d2c80d8
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "29x29",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "40x40",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "iphone",
+ "size" : "60x60",
+ "scale" : "3x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "29x29",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "40x40",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "ipad",
+ "size" : "76x76",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+} \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/Info.plist b/src/ui/ios/gf-ios-swift/Info.plist
new file mode 100644
index 000000000..3fe4443b4
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Info.plist
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>GF.$(PRODUCT_NAME:rfc1034identifier)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/src/ui/ios/gf-ios-swift/Languages.swift b/src/ui/ios/gf-ios-swift/Languages.swift
new file mode 100644
index 000000000..91f0a22a4
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Languages.swift
@@ -0,0 +1,39 @@
+import Foundation
+
+// A simple class for a language, mostly just containing its name etc.
+class Language {
+
+ let name: String
+ let abbreviation: String
+ let bcp: String
+
+ init(name: String, abbreviation: String, bcp: String) {
+ self.name = name
+ self.abbreviation = abbreviation
+ self.bcp = bcp
+ }
+
+}
+
+// If we need to set more languages in the app, this is the place to do it!
+// Don't forget to also update the grammars folder :)
+let allLanguages: Array<Language> = [
+ Language(name: "Bulgarian", abbreviation: "Bul", bcp: "en-GB"), // lacks bcp-47 code in iOS, would be bg-BG
+ Language(name: "Chinese", abbreviation: "Chi", bcp: "zh-CN"),
+ Language(name: "Dutch", abbreviation: "Dut", bcp: "nl-NL"),
+ Language(name: "English", abbreviation: "Eng", bcp: "en-GB"),
+ Language(name: "Finnish", abbreviation: "Fin", bcp: "fi-FI"),
+ Language(name: "French", abbreviation: "Fre", bcp: "fr-FR"),
+ Language(name: "German", abbreviation: "Ger", bcp: "de-DE"),
+ Language(name: "Hindi", abbreviation: "Hin", bcp: "hi-IN"),
+ Language(name: "Italian", abbreviation: "Ita", bcp: "it-IT"),
+ Language(name: "Japanese", abbreviation: "Jpn", bcp: "ja-JP"),
+ Language(name: "Spanish", abbreviation: "Spa", bcp: "es-ES"),
+ Language(name: "Swedish", abbreviation: "Swe", bcp: "sv-SE"),
+ Language(name: "Thai", abbreviation: "Tha", bcp: "th-TH")
+
+]
+
+// Callback for morphological analysis - if it turns out this isn't needed, it could be removed
+func morphoCallback(callback: UnsafeMutablePointer<PgfMorphoCallback>, lemma: PgfCId, analysis: GuString, prob: prob_t, err: UnsafeMutablePointer<GuExn>) -> Void {
+} \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/Translator.swift b/src/ui/ios/gf-ios-swift/Translator.swift
new file mode 100644
index 000000000..bc4e1b232
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/Translator.swift
@@ -0,0 +1,210 @@
+import UIKit
+import Foundation
+
+class Translator {
+
+ // Create allocation pool, exception frame, and pgf grammar
+ var pool: COpaquePointer
+ var err: UnsafeMutablePointer<GuExn>
+ var out: UnsafeMutablePointer<GuOut>
+ let pgf: UnsafeMutablePointer<PgfPGF>
+
+ init() {
+ self.pool = gu_new_pool()
+ self.err = gu_new_exn(self.pool)
+ self.out = gu_file_out(stdout, self.pool)
+
+ // Read the PGF grammar.
+ var pgfBundle = NSBundle .mainBundle()
+ var pgfStr = pgfBundle .pathForResource("App", ofType: "pgf", inDirectory: "grammars")
+ self.pgf = pgf_read(pgfStr!, self.pool, self.err)
+ }
+
+ // Variable to hold concrete grammars
+ var grammars: [String: (name: String, concr: UnsafeMutablePointer<PgfConcr>)] = [
+ "to": ("", UnsafeMutablePointer<PgfConcr>()),
+ "from": ("", UnsafeMutablePointer<PgfConcr>()),
+ "previous": ("", UnsafeMutablePointer<PgfConcr>())
+ ]
+
+ // Loads a grammar (but returns it, doesn't save it)
+ func loadGrammar(language: String, destination: String) -> Void {
+
+ // Switch with the previously used language if applicable
+ if (language == self.grammars["previous"]!.name) {
+ return self.switchLanguages(destination, to: "third")
+ }
+
+ // Load the file
+ var pgfBundle = NSBundle .mainBundle()
+ var concr = pgf_get_language(self.pgf, "App\(language)")
+ var fileStr = pgfBundle.pathForResource("App\(language)", ofType: "pgf_c", inDirectory: "grammars")
+ var file = fopen(fileStr!, "r")
+
+ // Load the grammar
+ pgf_concrete_load(concr, gu_file_in(file, self.pool), self.err)
+ if (self.grammars[destination]!.concr != UnsafeMutablePointer<PgfConcr>()) {
+ pgf_concrete_unload(self.grammars[destination]!.concr)
+ }
+ self.grammars[destination]!.concr = nil
+ self.grammars[destination]!.concr = concr
+
+ }
+
+ // Switches place of from and to concrete grammars
+ func switchLanguages(from: String, to: String) -> Void {
+ let tmp = self.grammars[from]!
+ self.grammars[from] = self.grammars[to]!
+ self.grammars[to] = tmp
+ }
+
+ // Translate a string
+ func translate(phrase: String) -> String {
+
+ // Intialise temporary pools
+ var tmpPool = gu_new_pool()
+ var tmpErr = gu_new_exn(tmpPool)
+
+ // Try to parse it fully
+ var parse = self.parse(phrase, startCat: "Phr", tmpPool: tmpPool, tmpErr: tmpErr)
+ var translation = ""
+ if (parse != nil) {
+ translation = self.linearize(parse, tmpPool: tmpPool, tmpErr: tmpErr)
+ } else {
+ translation = self.translateByLookup(phrase)
+ }
+
+ // Clear up resources
+ gu_exn_clear(tmpErr)
+ gu_pool_free(tmpPool)
+ tmpPool = nil
+ tmpErr = nil
+
+ return translation
+ }
+
+ // Translates by lookup - that is, each word by itself
+ func translateByLookup(phrase: String) -> String {
+ var translation = "%"
+ var words = phrase.componentsSeparatedByString(" ")
+ for word in words {
+ translation += self.translateWord(word) + " "
+ }
+ return translation
+ }
+
+ // Translates a single word - differs from phrase translation as it does morphological analysis
+ func translateWord(word: String) -> String {
+
+ // Intialise temporary pools
+ var tmpPool = gu_new_pool()
+ var tmpErr = gu_new_exn(tmpPool)
+
+ // Try to parse it fully
+ var parse = self.parse(word, startCat: "Chunk", tmpPool: tmpPool, tmpErr: tmpErr)
+ var translation = ""
+ if (parse != nil) {
+ translation = self.linearize(parse, tmpPool: tmpPool, tmpErr: tmpErr)
+ } else {
+
+ // Full parse didn't work; try to make a morphological analysis
+
+ // This functionality is currently missing due to pointer hell in Swift.
+ // The full temporary code is commented out below. For now, we just return the uppercased translation instead.
+ translation = word.uppercaseString
+
+// var lcWord = word.lowercaseString
+// var p = UnsafeMutablePointer<(UnsafeMutablePointer<PgfMorphoCallback>, PgfCId, GuString, prob_t, UnsafeMutablePointer<GuExn>) -> ()>.alloc(1)
+// p.initialize(morphoCallback)
+// var cp = COpaquePointer(p)
+// var fp = CFunctionPointer<(UnsafeMutablePointer<PgfMorphoCallback>, PgfCId, GuString, prob_t, UnsafeMutablePointer<GuExn>) -> ()>(cp)
+// var callback = PgfMorphoCallback(callback: fp)
+// pgf_lookup_morpho(self.grammars["from"]!.concr, word, &callback, tmpErr)
+// if (gu_ok(tmpErr)) {
+// var analyses = []
+// for analysis in analyses {
+// var lemma = ???
+// if (self.hasLinearization(lemma)) {
+// var sbuf = gu_string_buf(tmpPool)
+// var sbufstr = gu_string_buf_freeze(sbuf, tmpPool)
+// var tmpIn = gu_data_in(String(UTF8String: sbufstr)!, strlen(sbufstr), tmpPool)
+// var expr = pgf_read_expr(tmpIn, tmpPool, tmpErr)
+// var ep = PgfExprProb(prob: 0, expr: expr)
+// translation = self.linearize(ep, tmpPool: tmpPool, tmpErr: tmpErr)
+// break
+// }
+// }
+// } else {
+ // Morphological analysis failed as well - just use the uppercased version of the word
+// translation = word.uppercaseString
+// }
+
+ }
+
+ // Clear up resources
+ gu_exn_clear(tmpErr)
+ gu_pool_free(tmpPool)
+ tmpPool = nil
+ tmpErr = nil
+
+ return translation
+
+ }
+
+ // Checks whether a linearization exists for a given analysis
+ func hasLinearization(analysis: PgfCId) -> Bool {
+ var tmpPool = gu_new_pool()
+ var result = pgf_has_linearization(self.grammars["to"]!.concr, analysis)
+ gu_pool_free(tmpPool)
+ tmpPool = nil
+ return result
+ }
+
+ // Wrapper to perform a parse and return either the parse or
+ func parse(phrase: String, startCat: String, tmpPool: COpaquePointer, tmpErr: UnsafeMutablePointer<GuExn>) -> UnsafeMutablePointer<PgfExprEnum> {
+ var result = pgf_parse(self.grammars["from"]!.concr, startCat, phrase, tmpErr, tmpPool, tmpPool)
+ if (!gu_ok(tmpErr)) {
+ return nil
+ }
+ return result
+ }
+
+ // Takes a parse result and linearizes its first result
+ func linearize(result: UnsafeMutablePointer<PgfExprEnum>, tmpPool: COpaquePointer, tmpErr: UnsafeMutablePointer<GuExn>) -> String {
+
+ // Get the first result
+ var ep = UnsafeMutablePointer<PgfExprProb>()
+ gu_enum_next(result, &ep, tmpPool)
+ var parse = ep[0]
+
+ // Linearise it and convert it to a String literal
+ var sbuf = gu_string_buf(tmpPool)
+ var tmpOut = gu_string_buf_out(sbuf)
+ pgf_linearize(self.grammars["to"]!.concr, parse.expr, tmpOut, tmpErr)
+ var translation = String(UTF8String: gu_string_buf_freeze(sbuf, tmpPool))!
+
+ // Clear up and return
+ gu_out_flush(tmpOut, tmpErr)
+ tmpOut = nil
+ return translation
+
+ }
+
+ // Removes characters that shouldn't be in the translated string
+ func formatTranslation(translation: String) -> String {
+
+ if (translation.isEmpty) {
+ return translation
+ }
+ var copy = translation
+ var firstChar = String(Array(translation)[0])
+ if (firstChar == "%" || firstChar == "*" || firstChar == "+") {
+ copy = copy.substringFromIndex(advance(copy.startIndex, 1))
+ }
+ for char in ["[", "]", "_"] {
+ copy = copy.stringByReplacingOccurrencesOfString(char, withString: " ")
+ }
+ return copy
+
+ }
+} \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/ViewController.swift b/src/ui/ios/gf-ios-swift/ViewController.swift
new file mode 100644
index 000000000..5766b340d
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/ViewController.swift
@@ -0,0 +1,223 @@
+import UIKit
+import AVFoundation
+
+class ViewController: UIViewController {
+
+ // Set sizes used within the controller
+ let maxBoxWidth: CGFloat = 150
+ let boxOffset: CGFloat = 10
+ var scrollViewHeight: CGFloat = 0
+ var scrollViewWidth: CGFloat = 0
+
+ // Translation properties
+ let translator = Translator()
+ let colors = Colors()
+ let synthesizer = AVSpeechSynthesizer()
+ let languages = allLanguages
+ var inLanguage: Language!
+ var outLanguage: Language!
+
+ // Initiate picker views
+ let inPicker: UIPickerView = UIPickerView()
+ let outPicker: UIPickerView = UIPickerView()
+
+ // UI connections
+ @IBOutlet weak var translationsView: UIScrollView!
+ @IBOutlet weak var inLanguageField: UITextField!
+ @IBOutlet weak var outLanguageField: UITextField!
+
+ // Reverses to and from languages
+ @IBAction func switchLanguages(sender: UIButton) {
+ self.inLanguageField.text = self.outLanguage.name
+ self.outLanguageField.text = self.inLanguage.name
+ let tmpLanguage = self.inLanguage
+ self.inLanguage = self.outLanguage
+ self.outLanguage = tmpLanguage
+ self.translator.switchLanguages("from", to: "to") // to also switch concrete grammars
+ }
+
+ // Creates a new translation field when the user clicks the keyboard icon
+ @IBAction func openKeyboard(sender: UIButton) {
+ let field = createFromView()
+ field.becomeFirstResponder()
+ }
+
+ // Starts the speech recognition process when the user clicks the microphone icon
+ @IBAction func openMicrophone(sender: UIButton) {
+ self.createToView("microphone support is on its way")
+ }
+
+ // Sums the heights of all translation boxes + margins
+ func calculateTranslationHeights() -> CGFloat {
+ var height = CGFloat(-29.5) // compensate for invisible scrollbars
+ for view in self.translationsView.subviews {
+ height += view.frame.height + self.boxOffset
+ }
+ height += self.boxOffset
+ return height
+ }
+
+ // Creates a new "to translate" view and returns it for placement
+ func createFromView() -> UITextView {
+ let view = UITextView(frame: CGRect(
+ x: self.boxOffset,
+ y: self.calculateTranslationHeights(),
+ width: 20,
+ height: 30
+ ))
+ view.backgroundColor = self.colors.backgroundColors["source"]
+ view.layer.cornerRadius = 5
+ view.returnKeyType = UIReturnKeyType.Done
+
+ view.delegate = self
+ self.translationsView.addSubview(view)
+ self.resizeTranslationsView()
+ return view
+ }
+
+ // A UIScrollView doesn't know its own height; use this method to resize it
+ func resizeTranslationsView() -> Void {
+ var subviewHeights = self.calculateTranslationHeights()
+ if (subviewHeights > self.scrollViewHeight) {
+ self.translationsView.contentSize = CGSizeMake(
+ self.translationsView.frame.width, subviewHeights)
+
+ // Also scroll to bottom
+ let offset = self.translationsView.contentSize.height - self.translationsView.bounds.size.height
+ let point = CGPointMake(0, offset)
+ self.translationsView.setContentOffset(point, animated: true)
+
+ }
+ }
+
+ // Creates and places a new "translated" view given the translated text
+ func createToView(translation: String) -> Void {
+ let view = UITextView(frame: CGRect(
+ x: 0, // we set the real value later a few lines down below
+ y: self.calculateTranslationHeights(),
+ width: self.maxBoxWidth,
+ height: 30
+ ))
+ view.backgroundColor = self.colors.translationToUIColor(translation)
+ view.text = self.translator.formatTranslation(translation)
+ view.layer.cornerRadius = 5
+ view.sizeToFit()
+
+ // The view has been set, so we can now move it further to the right
+ view.frame.origin.x = self.scrollViewWidth - 10 - view.frame.width
+ self.translationsView.addSubview(view)
+ self.resizeTranslationsView()
+ }
+
+ // Perform translation
+ func translate(phrase: String) -> Void {
+ var translation = self.translator.translate(phrase)
+ self.createToView(translation)
+
+ // Run TTS except in simulator (because the simulator doesn't support it)
+ #if !arch(i386) && !arch(x86_64)
+ var utterance = AVSpeechUtterance(string: translation)
+ utterance.voice = AVSpeechSynthesisVoice(language: self.outLanguage.bcp)
+ self.synthesizer.speakUtterance(utterance)
+ #endif
+
+ }
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Set size variables (-162 for the height to compensate for the views above the scrollview)
+ self.scrollViewHeight = UIScreen.mainScreen().bounds.height - 162
+ self.scrollViewWidth = UIScreen.mainScreen().bounds.width
+
+ // Load view pickers
+ self.inPicker.delegate = self
+ self.inPicker.dataSource = self
+
+ self.outPicker.delegate = self
+ self.outPicker.dataSource = self
+
+ self.inLanguageField.inputView = inPicker
+ self.outLanguageField.inputView = outPicker
+
+ // Load initial languages
+ self.inLanguage = self.languages[3] // English
+ self.outLanguage = self.languages[10] // Swedish
+ self.translator.loadGrammar("Eng", destination: "from")
+ self.translator.loadGrammar("Swe", destination: "to")
+
+ }
+
+ override func didReceiveMemoryWarning() {
+ super.didReceiveMemoryWarning()
+ }
+
+}
+
+// On selection functionality for the view pickers
+extension ViewController: UIPickerViewDelegate {
+
+ // Makes the picker views return a language rather than text
+ func pickerView(pickerView: UIPickerView!, titleForRow row: Int, forComponent component: Int) -> String
+ {
+ return self.languages[row].name
+ }
+
+ // Row was selected: update buttons, load new grammar
+ func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+ var language = self.languages[row]
+
+ var fromTo: String
+ var textField = UITextField()
+ if (pickerView == self.inPicker) {
+ self.inLanguage = language
+ fromTo = "from"
+ textField = self.inLanguageField
+ } else {
+ self.outLanguage = language
+ fromTo = "to"
+ textField = self.outLanguageField
+ }
+
+ self.translator.loadGrammar(language.abbreviation, destination: fromTo)
+ textField.text = language.name
+ textField.resignFirstResponder()
+ }
+
+}
+
+// View picker layout functionality
+extension ViewController: UIPickerViewDataSource {
+
+ func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
+ return 1
+ }
+
+ func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+ return self.languages.count
+ }
+
+}
+
+// Keyboard input functionality
+extension ViewController: UITextViewDelegate {
+
+ // Method is called on keypress
+ func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
+
+ // UITextViews lack a method to close keyboard on enter, so we use this slight hack
+ if (text == "\n") {
+ textView.resignFirstResponder()
+ self.translate(textView.text)
+ return false
+ }
+
+ // But if it wasn't the enter key, we resize the UITextView dynamically
+ textView.sizeToFit()
+ if (textView.frame.size.width < self.maxBoxWidth) {
+ textView.frame.size.width += 10
+ }
+ return true
+ }
+
+} \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/gf-ios-swift-Bridging-Header.h b/src/ui/ios/gf-ios-swift/gf-ios-swift-Bridging-Header.h
new file mode 100644
index 000000000..76ccbefde
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/gf-ios-swift-Bridging-Header.h
@@ -0,0 +1,13 @@
+#ifndef gf_ios_swift_gf_ios_swift_Bridging_Header_h
+#define gf_ios_swift_gf_ios_swift_Bridging_Header_h
+
+#import "pgf/pgf.h"
+#import "gu/file.h"
+#import "gu/variant.h"
+#import "gu/map.h"
+#import "gu/enum.h"
+#import "gu/exn.h"
+#import "pgf/literals.h"
+#import "pgf/linearizer.h"
+
+#endif \ No newline at end of file
diff --git a/src/ui/ios/gf-ios-swift/images/ic_action_switch.png b/src/ui/ios/gf-ios-swift/images/ic_action_switch.png
new file mode 100644
index 000000000..5449a32b8
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/images/ic_action_switch.png
Binary files differ
diff --git a/src/ui/ios/gf-ios-swift/images/ic_app copy.png b/src/ui/ios/gf-ios-swift/images/ic_app copy.png
new file mode 100644
index 000000000..8d9bdd820
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/images/ic_app copy.png
Binary files differ
diff --git a/src/ui/ios/gf-ios-swift/images/ic_app.png b/src/ui/ios/gf-ios-swift/images/ic_app.png
new file mode 100644
index 000000000..a9b17ee1c
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/images/ic_app.png
Binary files differ
diff --git a/src/ui/ios/gf-ios-swift/images/ic_keyboard.png b/src/ui/ios/gf-ios-swift/images/ic_keyboard.png
new file mode 100644
index 000000000..ce257a269
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/images/ic_keyboard.png
Binary files differ
diff --git a/src/ui/ios/gf-ios-swift/images/ic_mic.png b/src/ui/ios/gf-ios-swift/images/ic_mic.png
new file mode 100644
index 000000000..f79ff489b
--- /dev/null
+++ b/src/ui/ios/gf-ios-swift/images/ic_mic.png
Binary files differ