Initial working commit

This commit is contained in:
IamTheFij 2018-01-17 21:41:18 -08:00
parent c300551c46
commit 664af77477
16 changed files with 1369 additions and 2 deletions

5
.gitignore vendored
View File

@ -57,9 +57,9 @@ xcuserdata
# #
# We recommend against adding the Pods directory to your .gitignore. However # We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at: # you should judge for yourself, the pros and cons are mentioned at:
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
# #
# Pods/ Pods/
# Carthage # Carthage
# #
@ -68,3 +68,4 @@ xcuserdata
Carthage/Build Carthage/Build
.DS_Store

View File

@ -0,0 +1,429 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 48;
objects = {
/* Begin PBXBuildFile section */
0C314390200143FE00E214BA /* FitbitClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C31438F200143FE00E214BA /* FitbitClient.swift */; };
0CC8D443200C69EF00480D5D /* FitbitWeight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC8D442200C69EF00480D5D /* FitbitWeight.swift */; };
0CC8D446200C6EB900480D5D /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0CC8D445200C6EB900480D5D /* HealthKit.framework */; };
0CC8D448200C73D000480D5D /* HealthKitHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC8D447200C73D000480D5D /* HealthKitHelper.swift */; };
0CC990EB20013C3D00624436 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC990EA20013C3D00624436 /* AppDelegate.swift */; };
0CC990ED20013C3D00624436 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC990EC20013C3D00624436 /* ViewController.swift */; };
0CC990F020013C3D00624436 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0CC990EE20013C3D00624436 /* Main.storyboard */; };
0CC990F220013C3D00624436 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0CC990F120013C3D00624436 /* Assets.xcassets */; };
0CC990F520013C3D00624436 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0CC990F320013C3D00624436 /* LaunchScreen.storyboard */; };
A06966DFF86C6A1D956E3384 /* Pods_FitKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F45705BC26BDA2DDAB016F60 /* Pods_FitKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
0C31438F200143FE00E214BA /* FitbitClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitbitClient.swift; sourceTree = "<group>"; };
0CC8D442200C69EF00480D5D /* FitbitWeight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FitbitWeight.swift; sourceTree = "<group>"; };
0CC8D444200C6EB900480D5D /* FitKit.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FitKit.entitlements; sourceTree = "<group>"; };
0CC8D445200C6EB900480D5D /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
0CC8D447200C73D000480D5D /* HealthKitHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthKitHelper.swift; sourceTree = "<group>"; };
0CC990E720013C3D00624436 /* FitKit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FitKit.app; sourceTree = BUILT_PRODUCTS_DIR; };
0CC990EA20013C3D00624436 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
0CC990EC20013C3D00624436 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
0CC990EF20013C3D00624436 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
0CC990F120013C3D00624436 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
0CC990F420013C3D00624436 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
0CC990F620013C3D00624436 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
1EC11871EC6B6830CA17FD35 /* Pods-FitKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FitKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-FitKit/Pods-FitKit.release.xcconfig"; sourceTree = "<group>"; };
BA903BC284979D6B461A77EC /* Pods-FitKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FitKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FitKit/Pods-FitKit.debug.xcconfig"; sourceTree = "<group>"; };
F45705BC26BDA2DDAB016F60 /* Pods_FitKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FitKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
0CC990E420013C3D00624436 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A06966DFF86C6A1D956E3384 /* Pods_FitKit.framework in Frameworks */,
0CC8D446200C6EB900480D5D /* HealthKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
0CC990DE20013C3D00624436 = {
isa = PBXGroup;
children = (
0CC990E920013C3D00624436 /* FitKit */,
0CC990E820013C3D00624436 /* Products */,
B1383E672235FD59368B2A65 /* Pods */,
BE7D9CC7BCC66E086BEBA09B /* Frameworks */,
);
sourceTree = "<group>";
};
0CC990E820013C3D00624436 /* Products */ = {
isa = PBXGroup;
children = (
0CC990E720013C3D00624436 /* FitKit.app */,
);
name = Products;
sourceTree = "<group>";
};
0CC990E920013C3D00624436 /* FitKit */ = {
isa = PBXGroup;
children = (
0CC8D444200C6EB900480D5D /* FitKit.entitlements */,
0C31438F200143FE00E214BA /* FitbitClient.swift */,
0CC990EA20013C3D00624436 /* AppDelegate.swift */,
0CC990EC20013C3D00624436 /* ViewController.swift */,
0CC990EE20013C3D00624436 /* Main.storyboard */,
0CC990F120013C3D00624436 /* Assets.xcassets */,
0CC990F320013C3D00624436 /* LaunchScreen.storyboard */,
0CC990F620013C3D00624436 /* Info.plist */,
0CC8D442200C69EF00480D5D /* FitbitWeight.swift */,
0CC8D447200C73D000480D5D /* HealthKitHelper.swift */,
);
path = FitKit;
sourceTree = "<group>";
};
B1383E672235FD59368B2A65 /* Pods */ = {
isa = PBXGroup;
children = (
BA903BC284979D6B461A77EC /* Pods-FitKit.debug.xcconfig */,
1EC11871EC6B6830CA17FD35 /* Pods-FitKit.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
};
BE7D9CC7BCC66E086BEBA09B /* Frameworks */ = {
isa = PBXGroup;
children = (
0CC8D445200C6EB900480D5D /* HealthKit.framework */,
F45705BC26BDA2DDAB016F60 /* Pods_FitKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
0CC990E620013C3D00624436 /* FitKit */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0CC990F920013C3D00624436 /* Build configuration list for PBXNativeTarget "FitKit" */;
buildPhases = (
E5B50210F26B509CB1E5FF42 /* [CP] Check Pods Manifest.lock */,
0CC990E320013C3D00624436 /* Sources */,
0CC990E420013C3D00624436 /* Frameworks */,
0CC990E520013C3D00624436 /* Resources */,
6D2599DAA5282C983E684673 /* [CP] Embed Pods Frameworks */,
8BEED8791DD8D7C053FA9DA5 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = FitKit;
productName = FitKit;
productReference = 0CC990E720013C3D00624436 /* FitKit.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
0CC990DF20013C3D00624436 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 0910;
ORGANIZATIONNAME = iamthefij;
TargetAttributes = {
0CC990E620013C3D00624436 = {
CreatedOnToolsVersion = 9.1;
ProvisioningStyle = Automatic;
SystemCapabilities = {
com.apple.HealthKit = {
enabled = 1;
};
};
};
};
};
buildConfigurationList = 0CC990E220013C3D00624436 /* Build configuration list for PBXProject "FitKit" */;
compatibilityVersion = "Xcode 8.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 0CC990DE20013C3D00624436;
productRefGroup = 0CC990E820013C3D00624436 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
0CC990E620013C3D00624436 /* FitKit */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
0CC990E520013C3D00624436 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0CC990F520013C3D00624436 /* LaunchScreen.storyboard in Resources */,
0CC990F220013C3D00624436 /* Assets.xcassets in Resources */,
0CC990F020013C3D00624436 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
6D2599DAA5282C983E684673 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FitKit/Pods-FitKit-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
8BEED8791DD8D7C053FA9DA5 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FitKit/Pods-FitKit-resources.sh\"\n";
showEnvVarsInLog = 0;
};
E5B50210F26B509CB1E5FF42 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
0CC990E320013C3D00624436 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0CC8D448200C73D000480D5D /* HealthKitHelper.swift in Sources */,
0CC990ED20013C3D00624436 /* ViewController.swift in Sources */,
0CC990EB20013C3D00624436 /* AppDelegate.swift in Sources */,
0C314390200143FE00E214BA /* FitbitClient.swift in Sources */,
0CC8D443200C69EF00480D5D /* FitbitWeight.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
0CC990EE20013C3D00624436 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
0CC990EF20013C3D00624436 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
0CC990F320013C3D00624436 /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
0CC990F420013C3D00624436 /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
0CC990F720013C3D00624436 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
0CC990F820013C3D00624436 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
0CC990FA20013C3D00624436 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = BA903BC284979D6B461A77EC /* Pods-FitKit.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = FitKit/FitKit.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = Y462XNRPU6;
INFOPLIST_FILE = FitKit/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.iamthefij.FitKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
0CC990FB20013C3D00624436 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 1EC11871EC6B6830CA17FD35 /* Pods-FitKit.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = FitKit/FitKit.entitlements;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = Y462XNRPU6;
INFOPLIST_FILE = FitKit/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.iamthefij.FitKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
0CC990E220013C3D00624436 /* Build configuration list for PBXProject "FitKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0CC990F720013C3D00624436 /* Debug */,
0CC990F820013C3D00624436 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
0CC990F920013C3D00624436 /* Build configuration list for PBXNativeTarget "FitKit" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0CC990FA20013C3D00624436 /* Debug */,
0CC990FB20013C3D00624436 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 0CC990DF20013C3D00624436 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:FitKit.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:FitKit.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

54
FitKit/AppDelegate.swift Normal file
View File

@ -0,0 +1,54 @@
//
// AppDelegate.swift
// FitKit
//
// Created by Ian Fijolek on 1/6/18.
// Copyright © 2018 iamthefij. All rights reserved.
//
import UIKit
import OAuthSwift
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
// Captures OAuth callback response
if (url.host == "oauth-callback") {
OAuthSwift.handle(url: url)
}
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 invalidate graphics rendering callbacks. 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 active 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:.
}
}

View File

@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"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" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"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"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="ViewController" customModule="FitKit" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<navigationBar contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dnr-xm-IuA">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<items>
<navigationItem title="FitKit" id="2jc-hB-lMj"/>
</items>
</navigationBar>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Logged In" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="CoH-cd-flw">
<rect key="frame" x="38" y="99" width="298" height="45"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="37"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="IdY-jU-MBP">
<rect key="frame" x="16" y="152" width="343" height="495"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.</string>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<accessibility key="accessibilityConfiguration">
<accessibilityTraits key="traits" notEnabled="YES"/>
</accessibility>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
<connections>
<outlet property="loggedIn" destination="CoH-cd-flw" id="SUh-wA-KNF"/>
<outlet property="outputText" destination="IdY-jU-MBP" id="yl5-f9-Gws"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="117.59999999999999" y="118.29085457271366"/>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,8 @@
<?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>com.apple.developer.healthkit</key>
<true/>
</dict>
</plist>

164
FitKit/FitbitClient.swift Normal file
View File

@ -0,0 +1,164 @@
//
// Client.swift
// FitKit
//
// Created by Ian Fijolek on 1/6/18.
// Copyright © 2018 iamthefij. All rights reserved.
//
import Foundation
import Alamofire
import OAuthSwift
import Locksmith
import OAuthSwiftAlamofire
let ACCOUNT_NAME = "FitBit"
let SERVICE_NAME = "FitKit"
class FitbitClient {
private enum FitKitRequestError: Error {
case unableToParseResults
}
private var oauthClient: OAuth2Swift
private let consumerKey = "22CPJ4"
private let consumerSecret = "eb05e27f6aa224bcc1cf273119565b28"
private let callbackUrl = "fitkit://oauth-callback/fitbit"
private let authorizeUrl = "https://www.fitbit.com/oauth2/authorize"
private let accessTokenUrl = "https://api.fitbit.com/oauth2/token"
init() {
// Init inner client
self.oauthClient = OAuth2Swift(
consumerKey: self.consumerKey,
consumerSecret: self.consumerSecret,
authorizeUrl: self.authorizeUrl,
accessTokenUrl: self.accessTokenUrl,
responseType: "token"
)
// Set OAuth client to handle default sessions
let sessionManager = SessionManager.default
sessionManager.adapter = self.oauthClient.requestAdapter
sessionManager.retrier = self.oauthClient.requestAdapter
}
func loadStoredTokens() {
NSLog("Someday we'll load stored credentials here! Today is not that day")
/*
let (oAuthData, _) = Locksmith.loadDataForUserAccount(userAccount: ACCOUNT_NAME, inService: SERVICE_NAME)
if let accessToken = oAuthData?.objectForKey("accessToken") {
// We have the token!!
self.oauthClient.client.credentials.oauthToken = accessToken
} else {
// No token! Boo
}
*/
}
/// Check if the current client is already authorized
///
/// - Returns: True if the current client can make requests
func isAuthorized() -> Bool {
if self.oauthClient.client.credential.oauthToken != "" {
if self.oauthClient.client.credential.isTokenExpired() {
NSLog("Credentials are expired")
return false
}
return true
}
// TODO: This should probably return true sometimes
return false
}
/// Displays authorization screen for user in a Safari web view
///
/// - Parameters:
/// - viewController: the source controller to create the new Safari view
/// - success: Callback to be executed on success
/// - failure: Callback to be executed on failure
func authorize(viewController: UIViewController, success: @escaping () -> Void, failure: @escaping () -> Void) {
// Set webview handler
self.oauthClient.authorizeURLHandler = SafariURLHandler(
viewController: viewController,
oauthSwift: self.oauthClient
)
// Authorize
let _ = self.oauthClient.authorize(
withCallbackURL: URL(string: self.callbackUrl)!,
scope: "weight activity",
state:"FITKIT", // TODO: make CSRF token
success: {
credential, response, parameters in
NSLog("Succesfully authenticated with credentials %s", credential.oauthToken)
success()
},
failure: {
error in
// TODO: Maybe return the error
NSLog(error.localizedDescription)
failure()
}
)
}
/// Request all weight data from Fitbit API for the current user beteween
/// two dates. Maximum allowed distance between the two dates is 31 days.
///
/// - Parameters:
/// - start: initial date to search from
/// - end: last date to include in search
/// - handler: function to handle the response
func getWeight(start: Date, end: Date, handler: @escaping (DataResponse<Any>) -> Void) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let urlString = "https://api.fitbit.com/1/user/-/body/log/weight/date/\(dateFormatter.string(from: start))/\(dateFormatter.string(from: end)).json"
NSLog("Maing request to \(urlString)")
Alamofire.request(urlString)
.validate(contentType: ["application/json"])
.responseJSON(completionHandler: handler)
}
/// Handler that parses responses from Fitbit Weight API and provides
/// instances of FitbitWeight to a new callback.
///
/// - Parameters:
/// - response: Original DataResponse<Any> from the Fitbit request
/// - callback: Callback that will accept a list of FitbitWeight instances
/// and an Error if, if present.
class func weightResponseHandler(response: DataResponse<Any>, callback: @escaping ([FitbitWeight]?, Error?) -> Void) {
NSLog("Handling weight response \(response.debugDescription)")
guard response.result.isSuccess else {
NSLog("Error while fetching weight: \(response.result.error!)")
callback(nil, response.result.error)
return
}
NSLog("Got weight! \(response.result.value!)")
guard let results = response.result.value as? [String: Any] else {
print("Return result not in form [String: Any]")
callback(nil, FitKitRequestError.unableToParseResults)
return
}
guard let weights = results["weight"] as? [[String: Any]] else {
print("Return result does not contain a list of weights")
callback(nil, FitKitRequestError.unableToParseResults)
return
}
var fbWeights = [FitbitWeight]()
for weight in weights {
fbWeights.append(FitbitWeight(withResult: weight))
}
callback(fbWeights, nil)
}
}

131
FitKit/FitbitWeight.swift Normal file
View File

@ -0,0 +1,131 @@
//
// FitbitWeight.swift
// FitKit
//
// Created by Ian Fijolek on 1/14/18.
// Copyright © 2018 iamthefij. All rights reserved.
//
import Foundation
import HealthKit
/// FitSample is a generic protocol that represents a datum from the Fitbit API
protocol FitSample {
/// The integer ID from Fitbit for the given sample
var logId: Int { get }
/// The Fitbit source device
var source: String { get }
/// The date that this sample was taken
var date: Date { get }
/// Creates a HealthKit Sample from the Fitbit object
///
/// - Returns: New HKSample instance that can be added to HealthKit
func makeSample() -> HKSample
/// Generates a metadata dictionary to be stored in HealthKit
///
/// - Returns: Map of custom metadata keys to their values
func metadata() -> [String: Any]
/// Returns the HKSampleType for the FitSample for querying or creating of
/// new instances.
///
/// - Returns: <#return value description#>
func getSampleType() -> HKSampleType
/// Returns a predicate that can be used to retrieve this instance from
/// HealthKit, if it has been added.
///
/// - Returns: A new NSPredicate that can filter results of an HKSampleQuery
func getPredicate() -> NSPredicate
}
// MARK: - Extension FitSample to return a metdata dictionary
extension FitSample {
/// Returns default set of metadata for a FitSample
///
/// - Returns: dictionary including the Fitbit source and Id
func metadata() -> [String: Any] {
return [
"Fitbit Source": self.source,
"Fitbit Id": self.logId
]
}
/// Returns predicate filtering results based on the "Fitbit Id" metadata
///
/// - Returns: A new NSPredicate that should return an equivalent sample
/// from HealthKit
func getPredicate() -> NSPredicate {
return HKQuery.predicateForObjects(
withMetadataKey: "Fitbit Id",
operatorType: .equalTo,
value: self.logId
)
}
}
/// FitbitWeight is a concrete subclass of FitSample. It represents a weigh
/// measurement retrieved from the Fitbit
class FitbitWeight: FitSample {
var weight: Double
var bmi: Double
var logId: Int
var source: String
var date: Date
/// Convenience initializer to create a FitbitWeight based on a response
/// from the api
///
/// - Parameter result: [String: Any?] dictionary response from the API
convenience init(withResult result: [String: Any?]) {
// TODO: Probably unsafe, but Swift...
NSLog("Trying to initialize \(result)")
let dateStringFormatter = DateFormatter()
dateStringFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let dateString = result["date"] as! String
let timeString = result["time"] as! String
let weighDate = dateStringFormatter.date(from: "\(dateString) \(timeString)")!
self.init(
withWeight: result["weight"] as! Double,
withBMI: result["bmi"] as! Double,
withDate: weighDate,
withLogId: result["logId"] as! Int,
withSource: result["source"] as! String
)
}
/// Initializes a FitbitWeight with specified properties
///
/// - Parameters:
/// - weight: Double weight value in kg
/// - bmi: Double bmi value (Might go away)
/// - date: Date that the measurement was taken
/// - logId: Int ID from Fitbit
/// - source: String source name from Fitbit
init(withWeight weight: Double, withBMI bmi: Double, withDate date: Date, withLogId logId: Int, withSource source: String) {
self.weight = weight
self.bmi = bmi
self.date = date
self.logId = logId
self.source = source
}
func getSampleType() -> HKSampleType {
// Forcing as we should be handling permissions
return HKObjectType.quantityType(forIdentifier: .bodyMass)!
}
func makeSample() -> HKSample {
let weight = HKQuantity(unit: HKUnit.gramUnit(with: .kilo), doubleValue: self.weight)
return HKQuantitySample(
// Forcing downcast since each concrete class implementation should provide compatible types
type: self.getSampleType() as! HKQuantityType,
quantity: weight,
start: self.date,
end: self.date,
metadata: self.metadata()
)
}
}

View File

@ -0,0 +1,199 @@
//
// HealthKitHelper.swift
// FitKit
//
// Created by Ian Fijolek on 1/14/18.
// Copyright © 2018 iamthefij. All rights reserved.
//
import Foundation
import HealthKit
/// Helper class with class functions to simplify interraction with HealthKit
class HealthKitHelper {
private enum HealthKitSetupError: Error {
case hkUnavailable
case dataTypeUnavailable
}
private enum FitKitSaveError: Error {
case sampleAlreadyExists
}
/// Attempts to authorize the application with HealthKit
/// If HealthKit is unavailable or access is not provided to the requested
/// types, an error will be passed into the callback.
///
/// - Parameter callback: Callback function for handling missing or unprovided
/// access.
class func authorizeHealthKit(_ callback: @escaping (Bool, Error?) -> Void) {
// Verify Health data is available
guard HKHealthStore.isHealthDataAvailable() else {
callback(false, HealthKitSetupError.hkUnavailable)
return
}
// This is not very well abstracted yet
guard let bodyMass = HKObjectType.quantityType(forIdentifier: .bodyMass) else {
callback(false, HealthKitSetupError.dataTypeUnavailable)
return
}
// Request access to the specified metrics
HKHealthStore().requestAuthorization(
toShare: [bodyMass],
read: [bodyMass],
completion: callback
)
}
/// Simplifies querying for HealthKit samples given types and predicates.
/// This is executed async and will dispatch the callback on the main thread
///
/// - Parameters:
/// - sampleType: The HKSampleType that you wish to query
/// - predicate: An NSPredicate to use in the query
/// - limit: Return a limited number of samples
/// - callback: Callback to be executed on the main thread
class func querySamplesWithPredicate(
for sampleType: HKSampleType,
withPredicate predicate: NSPredicate,
limit: Int,
callback: @escaping ([HKQuantitySample]?, Error?) -> Void)
{
let sortDescriptor = NSSortDescriptor(
key: HKSampleSortIdentifierStartDate,
ascending: false
)
let sampleQuery = HKSampleQuery(
sampleType: sampleType,
predicate: predicate,
limit: limit,
sortDescriptors: [sortDescriptor]
) {
(query, samples, error) in
DispatchQueue.main.async {
guard let samples = samples as? [HKQuantitySample] else {
callback(nil, error)
return
}
callback(samples, error)
return
}
}
HKHealthStore().execute(sampleQuery)
}
/// Provides easy query for the most recent sample of a given type
///
/// - Parameters:
/// - sampleType: The HKSampleType that you wish to query
/// - callback: Callback to be executed on the main thread
class func queryMostRecentSample(for sampleType: HKSampleType, callback: @escaping (HKQuantitySample?, Error?) -> Void) {
let mostRecentPredicate = HKQuery.predicateForSamples(
withStart: Date.distantPast,
end: Date(),
options: .strictStartDate
)
self.querySamplesWithPredicate(for: sampleType, withPredicate: mostRecentPredicate, limit: 1) {
samples, error in
guard let sample = samples?.first else {
callback(nil, error)
return
}
callback(sample, nil)
return
}
}
/// Simple query for a HealthKit sample of a given type and Fitbit Id
///
/// - Parameters:
/// - sampleType: The HKSampleType that you wish to query
/// - logId: The Fitbit logId to be queried for (FitSample.logId)
/// - callback: Callback to be executed on the main thread
class func getSampleWithFitbitId(for sampleType: HKSampleType, withId logId: Int, callback: @escaping (HKQuantitySample?, Error?) -> Void) {
let logIdEquals = HKQuery.predicateForObjects(withMetadataKey: "Fitbit Id", operatorType: .equalTo, value: logId)
self.querySamplesWithPredicate(for: sampleType, withPredicate: logIdEquals, limit: 1) {
samples, error in
guard let sample = samples?.first else {
callback(nil, error)
return
}
callback(sample, nil)
return
}
}
/// Simple query for a HealthKit for a given FitSample
///
/// - Parameters:
/// - fitSample: The FitSample that you wish to query for in HealthKit
/// - callback: Callback to be executed on the main thread
class func querySample(for fitSample: FitSample, callback: @escaping (HKQuantitySample?, Error?) -> Void) {
self.querySamplesWithPredicate(
for: fitSample.getSampleType(),
withPredicate: fitSample.getPredicate(),
limit: 1
) {
samples, error in
guard let sample = samples?.first else {
callback(nil, error)
return
}
callback(sample, nil)
return
}
}
/// Save a FitSample to HealthKit
///
/// - Parameters:
/// - sample: FitSample instance to save
/// - callback: Callback function that will accept the completion from
/// HealthKit
class func saveFitSample(_ sample: FitSample, callback: @escaping (Bool, Error?) -> Void) {
HKHealthStore().save(sample.makeSample(), withCompletion: callback)
}
/// Idempotent save of a FitSample to HealthKit. Before saving, executes an
/// async query for the sample in HealthKit. If found, it will not save.
///
/// - Parameters:
/// - sample: FitSample instance to save
/// - callback: Callback funciton that accepts parameters indicating a
/// success or failure and any error that is returned. In the case of an
/// existing sample, it will be designated a success, but there it will
/// also return a FitKitSaveError.sampleAlreadyExists error.
class func maybeSaveSample(_ sample: FitSample, callback: @escaping (Bool, Error?) -> Void) {
self.querySample(for: sample) {
(existingSample, error) in
if let error = error {
NSLog("Error querying for sample")
callback(false, error)
return
}
if existingSample == nil {
// Save only if no existing sample was found
self.saveFitSample(sample) {
(success, error) in
if let error = error {
NSLog("Error Saving Sample: \(error.localizedDescription)")
callback(false, error)
return
} else {
NSLog("Successfully saved Sample")
callback(true, nil)
return
}
}
} else {
NSLog("Sample already found in HealthKit. Will not save")
callback(true, FitKitSaveError.sampleAlreadyExists)
return
}
}
}
}

60
FitKit/Info.plist Normal file
View File

@ -0,0 +1,60 @@
<?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>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</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>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>fitkit</string>
</array>
</dict>
</array>
<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>NSHealthShareUsageDescription</key>
<string>Will use HealthKit to write Fitbit data so you can manage your data locally</string>
<key>NSHealthUpdateUsageDescription</key>
<string>Will use HealthKit to write Fitbit data so you can manage your data locally</string>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,83 @@
//
// ViewController.swift
// FitKit
//
// Created by Ian Fijolek on 1/6/18.
// Copyright © 2018 iamthefij. All rights reserved.
//
import Alamofire
import HealthKit
import UIKit
class ViewController: UIViewController {
@IBOutlet var loggedIn: UILabel!
@IBOutlet var outputText: UITextView!
let client = FitbitClient()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
updateAuthorizedLabel()
}
override func viewDidAppear(_ animated: Bool) {
authorize()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
fileprivate func updateAuthorizedLabel() {
if self.client.isAuthorized() {
self.loggedIn.text = "Logged In"
} else {
self.loggedIn.text = "Not Logged In"
}
}
fileprivate func authorize() {
HealthKitHelper.authorizeHealthKit() {
result, error in
// TODO: Do something with the result and error
if !self.client.isAuthorized() {
self.client.authorize(viewController: self, success: self.authorized, failure: self.unauthroized)
}
}
}
fileprivate func authorized() {
NSLog("We have Fitbit auth!!!")
updateAuthorizedLabel()
self.displayWeight()
}
fileprivate func unauthroized() {
NSLog("No auth. Booo!!!")
}
func displayWeight() {
let today = Date()
let lastMonth = Calendar.current.date(byAdding: .day, value: -31, to: today)!
self.client.getWeight(start: lastMonth, end: today) {
response in FitbitClient.weightResponseHandler(response: response) {
fbWeights, error in
if let error = error {
NSLog("Unable to get weights: \(error)")
return
}
if let fbWeights = fbWeights {
for fbWeight in fbWeights {
NSLog("Got Weight: \(fbWeight.logId): \(fbWeight.date) \(fbWeight.weight)")
HealthKitHelper.maybeSaveSample(fbWeight, callback: {_,_ in})
}
}
}
}
}
}

15
Podfile Normal file
View File

@ -0,0 +1,15 @@
project 'FitKit.xcodeproj'
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'FitKit' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
pod "Alamofire"
pod "Locksmith"
pod 'OAuthSwift', '~> 1.2.0'
pod 'OAuthSwiftAlamofire'
end

23
Podfile.lock Normal file
View File

@ -0,0 +1,23 @@
PODS:
- Alamofire (4.5.1)
- Locksmith (4.0.0)
- OAuthSwift (1.2.0)
- OAuthSwiftAlamofire (0.1.0):
- Alamofire
- OAuthSwift
DEPENDENCIES:
- Alamofire
- Locksmith
- OAuthSwift (~> 1.2.0)
- OAuthSwiftAlamofire
SPEC CHECKSUMS:
Alamofire: 2d95912bf4c34f164fdfc335872e8c312acaea4a
Locksmith: e9bebbaaa4cee3c511bc358a44f843c3fe00e21f
OAuthSwift: 7fd6855b8e4d58eb5a30d156ea9bed7a8aecd1ca
OAuthSwiftAlamofire: 20bb998ac506f82c3bab0625fbc98a9c6fca783f
PODFILE CHECKSUM: 114ede9ace3efbf5222b8790a77f72e973a10d2b
COCOAPODS: 1.2.0