mirror of
https://github.com/scratchfoundation/scratchjr.git
synced 2025-08-28 22:18:54 -04:00
[WIP] Initial version with WKWebView
Upgrade webview to WKWebView with many thanks to @yueyuzhao * Need to debug a problem saving thumbnails on iOS 9.3.5 * Needs much more testing
This commit is contained in:
parent
927ebc9249
commit
6c737f51b8
17 changed files with 7944 additions and 465 deletions
|
@ -13,6 +13,9 @@
|
|||
204D80CC18A4140600ECBB8B /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80CA18A4140600ECBB8B /* InfoPlist.strings */; };
|
||||
204D80CE18A4140600ECBB8B /* ScratchJrTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 204D80CD18A4140600ECBB8B /* ScratchJrTests.m */; };
|
||||
7465F19843DCA9DDC62CC852 /* libPods-ScratchJr Free.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7ACBA0233542F96AC72D4D7E /* libPods-ScratchJr Free.a */; };
|
||||
8F8FEAA724E700FF00571717 /* JsBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F8FEAA624E700FF00571717 /* JsBridge.m */; };
|
||||
8F8FEAA924E703B000571717 /* JsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8F8FEAA824E703B000571717 /* JsRequest.m */; };
|
||||
8F8FEAAB24E7065E00571717 /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8F8FEAAA24E7065E00571717 /* View.xib */; };
|
||||
A7FDEB52BFEDCD9E4520FF89 /* libPods-ScratchJrTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 512C88D0E9938AB5972ACF38 /* libPods-ScratchJrTests.a */; };
|
||||
D92D0F951C33381B00C573AD /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2001BEB918E237AE008E563F /* MessageUI.framework */; };
|
||||
D92D0F961C33381B00C573AD /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 20B2227818A53688003BDE44 /* AVFoundation.framework */; };
|
||||
|
@ -22,7 +25,6 @@
|
|||
D92D0F9A1C33381B00C573AD /* libsqlite3.0.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */; };
|
||||
D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 20B2226818A4404B003BDE44 /* Settings.bundle */; };
|
||||
D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 204D80B118A4140600ECBB8B /* InfoPlist.strings */; };
|
||||
D92D0F9F1C33381B00C573AD /* View.xib in Resources */ = {isa = PBXBuildFile; fileRef = 204D80DC18A4195E00ECBB8B /* View.xib */; };
|
||||
D92D0FCA1C3346D200C573AD /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB31C33469A00C573AD /* AppDelegate.m */; };
|
||||
D92D0FCB1C3346D200C573AD /* CameraMask.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB41C33469A00C573AD /* CameraMask.m */; };
|
||||
D92D0FCC1C3346D200C573AD /* CameraView.m in Sources */ = {isa = PBXBuildFile; fileRef = D92D0FB51C33469A00C573AD /* CameraView.m */; };
|
||||
|
@ -62,7 +64,6 @@
|
|||
204D80C918A4140600ECBB8B /* ScratchJrTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "ScratchJrTests-Info.plist"; sourceTree = "<group>"; };
|
||||
204D80CB18A4140600ECBB8B /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
204D80CD18A4140600ECBB8B /* ScratchJrTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScratchJrTests.m; sourceTree = "<group>"; };
|
||||
204D80DC18A4195E00ECBB8B /* View.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = View.xib; sourceTree = "<group>"; };
|
||||
20B2226818A4404B003BDE44 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = SOURCE_ROOT; };
|
||||
20B2226E18A46327003BDE44 /* libsqlite3.0.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.0.dylib; path = usr/lib/libsqlite3.0.dylib; sourceTree = SDKROOT; };
|
||||
20B2227818A53688003BDE44 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
|
||||
|
@ -70,6 +71,9 @@
|
|||
5A3230A01AEC02BC09345F16 /* Pods-ScratchJrTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.debug.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
72E2C9BF3BE9F67099F0180F /* Pods-ScratchJrTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJrTests.release.xcconfig"; path = "Target Support Files/Pods-ScratchJrTests/Pods-ScratchJrTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
7ACBA0233542F96AC72D4D7E /* libPods-ScratchJr Free.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ScratchJr Free.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
8F8FEAA624E700FF00571717 /* JsBridge.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = JsBridge.m; path = src/JsBridge.m; sourceTree = "<group>"; };
|
||||
8F8FEAA824E703B000571717 /* JsRequest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = JsRequest.m; path = src/JsRequest.m; sourceTree = "<group>"; };
|
||||
8F8FEAAA24E7065E00571717 /* View.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = View.xib; path = src/View.xib; sourceTree = "<group>"; };
|
||||
97A526F2B60190BFC0BD3081 /* Pods-ScratchJr Free.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ScratchJr Free.debug.xcconfig"; path = "Target Support Files/Pods-ScratchJr Free/Pods-ScratchJr Free.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
D905DF721C220BA6003DA34A /* free */ = {isa = PBXFileReference; lastKnownFileType = folder; name = free; path = ../editions/free; sourceTree = "<group>"; };
|
||||
D92D0FA61C33381B00C573AD /* ScratchJr Free.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ScratchJr Free.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -166,7 +170,6 @@
|
|||
children = (
|
||||
D92D0FC91C3346AC00C573AD /* src */,
|
||||
204D80AF18A4140600ECBB8B /* Supporting Files */,
|
||||
204D80DC18A4195E00ECBB8B /* View.xib */,
|
||||
);
|
||||
path = ScratchJr;
|
||||
sourceTree = "<group>";
|
||||
|
@ -249,6 +252,9 @@
|
|||
D92D0FBC1C33469A00C573AD /* ViewController.h */,
|
||||
D92D0FBD1C33469A00C573AD /* ViewController.m */,
|
||||
D92D0FB81C33469A00C573AD /* main.m */,
|
||||
8F8FEAA624E700FF00571717 /* JsBridge.m */,
|
||||
8F8FEAA824E703B000571717 /* JsRequest.m */,
|
||||
8F8FEAAA24E7065E00571717 /* View.xib */,
|
||||
);
|
||||
name = App;
|
||||
sourceTree = "<group>";
|
||||
|
@ -310,7 +316,7 @@
|
|||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1110;
|
||||
ORGANIZATIONNAME = "Playful Invention Company";
|
||||
ORGANIZATIONNAME = "Scratch Foundation";
|
||||
TargetAttributes = {
|
||||
204D80BF18A4140600ECBB8B = {
|
||||
DevelopmentTeam = TTT4R28WVF;
|
||||
|
@ -354,9 +360,9 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D92D0F9D1C33381B00C573AD /* Settings.bundle in Resources */,
|
||||
8F8FEAAB24E7065E00571717 /* View.xib in Resources */,
|
||||
D965E3971C3EC63A005D792F /* Free-Images.xcassets in Resources */,
|
||||
D92D0F9E1C33381B00C573AD /* InfoPlist.strings in Resources */,
|
||||
D92D0F9F1C33381B00C573AD /* View.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -438,7 +444,9 @@
|
|||
files = (
|
||||
D92D0FCD1C3346DC00C573AD /* Database.m in Sources */,
|
||||
D92D0FCE1C3346DC00C573AD /* IO.m in Sources */,
|
||||
8F8FEAA724E700FF00571717 /* JsBridge.m in Sources */,
|
||||
D92D0FCF1C3346DC00C573AD /* main.m in Sources */,
|
||||
8F8FEAA924E703B000571717 /* JsRequest.m in Sources */,
|
||||
D92D0FD01C3346DC00C573AD /* RecordSound.m in Sources */,
|
||||
D92D0FD11C3346DC00C573AD /* ScratchJr.m in Sources */,
|
||||
D92D0FD21C3346DC00C573AD /* ViewController.m in Sources */,
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.iPad.XIB" version="3.0" toolsVersion="4514" systemVersion="12F45" targetRuntime="iOS.CocoaTouch.iPad" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="3747"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ViewController">
|
||||
<connections>
|
||||
<outlet property="view" destination="1" id="NLv-sC-BRg"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="1" customClass="UIWebView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="768" height="1004"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<simulatedStatusBarMetrics key="simulatedStatusBarMetrics" statusBarStyle="blackOpaque"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -283,9 +283,9 @@ NSMutableDictionary *soundtimers;
|
|||
NSString *soundName = [[timer userInfo] objectForKey:@"soundName"];
|
||||
if (sounds[soundName] == nil) return;
|
||||
NSString *callback = [NSString stringWithFormat:@"OS.soundDone('%@');", soundName];
|
||||
UIWebView *webview = [ViewController webview];
|
||||
WKWebView *webview = [ViewController webview];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webview stringByEvaluatingJavaScriptFromString:callback];
|
||||
[webview evaluateJavaScript:callback completionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
196
ios/ScratchJr/src/JsBridge.m
Normal file
196
ios/ScratchJr/src/JsBridge.m
Normal file
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// JsBridge.m
|
||||
// ScratchJr Free
|
||||
//
|
||||
// Created by Yueyu on 2020/7/31.
|
||||
//
|
||||
|
||||
#import "ScratchJr.h"
|
||||
#import <WebKit/WebKit.h>
|
||||
@import Firebase;
|
||||
|
||||
@implementation JsBridge
|
||||
|
||||
-(void) userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
|
||||
{
|
||||
NSDictionary *dict = (NSDictionary *)message.body;
|
||||
|
||||
JsRequest *request = [[JsRequest alloc] initWithDictionary:dict];
|
||||
|
||||
NSString *method = [request.method stringByAppendingString:@":"];
|
||||
SEL selector = NSSelectorFromString(method);
|
||||
if (![self respondsToSelector:selector]) {
|
||||
NSLog(@"method %@ not exists", request.method);
|
||||
return;
|
||||
}
|
||||
// [self performSelector:selector withObject:request];
|
||||
// to disable warning: PerformSelector may cause a leak because its selector is unknown
|
||||
// thanks wbyoung for https://stackoverflow.com/a/20058585
|
||||
IMP imp = [self methodForSelector:selector];
|
||||
void (*func)(id, SEL, JsRequest *) = (void *)imp;
|
||||
func(self, selector, request);
|
||||
}
|
||||
|
||||
-(void) askForPermission: (JsRequest *) request
|
||||
{
|
||||
[RecordSound setPermission];
|
||||
[request callback:@"ok"];
|
||||
}
|
||||
|
||||
-(void) database_stmt: (JsRequest *) request {
|
||||
[request callback:[Database stmt:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) database_query: (JsRequest *) request {
|
||||
[request callback:[Database query:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_getmd5: (JsRequest *) request {
|
||||
[request callback:[IO getMD5:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_getsettings: (JsRequest *) request {
|
||||
[request callback:[IO getsettings]];
|
||||
}
|
||||
|
||||
-(void) io_cleanassets: (JsRequest *) request {
|
||||
[IO cleanassets: request.params[0]];
|
||||
[request callback:@"ok"];
|
||||
}
|
||||
|
||||
-(void) io_setfile: (JsRequest *) request {
|
||||
[request callback:[IO setfile:request.params[0]:request.params[1]]];
|
||||
}
|
||||
|
||||
-(void) io_getfile: (JsRequest *) request {
|
||||
[request callback:[IO getfile:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_setmedia: (JsRequest *) request {
|
||||
[request callback:[IO setmedia:request.params[0] :request.params[1]]];
|
||||
}
|
||||
|
||||
-(void) io_setmedianame: (JsRequest *) request {
|
||||
[request callback:[IO setmedianame:request.params[0] :request.params[1] :request.params[2]]];
|
||||
}
|
||||
|
||||
-(void) io_getmedia: (JsRequest *) request {
|
||||
[request callback:[IO getmedia:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_getmediadata: (JsRequest *) request {
|
||||
int offset = [request.params[1] intValue];
|
||||
int length = [request.params[2] intValue];
|
||||
NSString *key = [NSString stringWithFormat:@"%@", request.params[0]];
|
||||
[request callback:[IO getmediadata:key :offset :length]];
|
||||
}
|
||||
|
||||
-(void) io_getmedialen: (JsRequest *) request {
|
||||
NSString *key = [NSString stringWithFormat:@"%@", request.params[1]];
|
||||
[request callback:[IO getmedialen:request.params[0] :key]];
|
||||
}
|
||||
|
||||
-(void) io_getmediadone: (JsRequest *) request {
|
||||
[request callback:[IO getmediadone:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_remove: (JsRequest *) request {
|
||||
[request callback:[IO remove:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_registersound: (JsRequest *) request {
|
||||
[request callback:[IO registerSound:request.params[0] :request.params[1]]];
|
||||
}
|
||||
|
||||
-(void) io_playsound: (JsRequest *) request {
|
||||
[request callback:[IO playSound:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) io_stopsound: (JsRequest *) request {
|
||||
[request callback:[IO stopSound:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) recordsound_recordstart: (JsRequest *) request {
|
||||
[request callback:[RecordSound startRecord]];
|
||||
}
|
||||
|
||||
-(void) recordsound_recordstop: (JsRequest *) request {
|
||||
[request callback:[RecordSound stopRecording]];
|
||||
}
|
||||
|
||||
-(void) recordsound_volume: (JsRequest *) request {
|
||||
[request callback:[NSString stringWithFormat:@"%f", [RecordSound getVolume]]];
|
||||
}
|
||||
|
||||
-(void) recordsound_startplay: (JsRequest *) request {
|
||||
[request callback:[RecordSound startPlay]];
|
||||
}
|
||||
|
||||
-(void) recordsound_stopplay: (JsRequest *) request {
|
||||
[request callback:[RecordSound stopPlay]];
|
||||
}
|
||||
|
||||
-(void) recordsound_recordclose: (JsRequest *) request {
|
||||
[request callback:[RecordSound recordclose:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) scratchjr_cameracheck: (JsRequest *) request {
|
||||
[request callback:[ScratchJr cameracheck]];
|
||||
}
|
||||
|
||||
-(void) scratchjr_has_multiple_cameras: (JsRequest *) request {
|
||||
[request callback:@"YES"];
|
||||
}
|
||||
|
||||
-(void) scratchjr_startfeed: (JsRequest *) request {
|
||||
[request callback:[ScratchJr startfeed: request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) scratchjr_stopfeed: (JsRequest *) request {
|
||||
[request callback:[ScratchJr stopfeed]];
|
||||
}
|
||||
|
||||
-(void) scratchjr_choosecamera: (JsRequest *) request {
|
||||
[request callback:[ScratchJr choosecamera:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) scratchjr_captureimage: (JsRequest *) request {
|
||||
[request callback:[ScratchJr captureimage:request.params[0]]];
|
||||
}
|
||||
|
||||
-(void) sendSjrUsingShareDialog: (JsRequest *) request {
|
||||
int shareType = [request.params[3] intValue];
|
||||
NSString *res = [IO sendSjrUsingShareDialog:request.params[0] :request.params[1] :request.params[2] :shareType : request.params[4]];
|
||||
[request callback:res];
|
||||
}
|
||||
|
||||
- (void) hideSplash: (JsRequest *) request {
|
||||
[request callback:[ScratchJr hideSplash:nil]];
|
||||
}
|
||||
|
||||
-(void) analyticsEvent: (JsRequest *) request {
|
||||
[FIRAnalytics logEventWithName:request.params[1] // action
|
||||
parameters:@{
|
||||
kFIRParameterItemName:request.params[2], // label
|
||||
kFIRParameterItemCategory:request.params[0] // category
|
||||
}];
|
||||
}
|
||||
|
||||
-(void) setAnalyticsPlacePref: (JsRequest *) request {
|
||||
[FIRAnalytics setUserPropertyString:request.params[0] forName:@"place_preference"];
|
||||
[request callback:@"ok"];
|
||||
}
|
||||
|
||||
// @param request like [name, propertyString]
|
||||
-(void) setAnalyticsPref: (JsRequest *) request {
|
||||
NSString *name = [NSString stringWithFormat:@"%@", request.params[0]];
|
||||
NSString *propertyString = [NSString stringWithFormat:@"%@", request.params[1]];
|
||||
[FIRAnalytics setUserPropertyString:propertyString forName:name];
|
||||
}
|
||||
|
||||
// iPad name (used for information in the name/sharing dialog to help people using Airdrop)
|
||||
- (void) deviceName: (JsRequest *) request {
|
||||
[request callback:[[UIDevice currentDevice] name]];
|
||||
}
|
||||
|
||||
@end
|
32
ios/ScratchJr/src/JsRequest.m
Normal file
32
ios/ScratchJr/src/JsRequest.m
Normal file
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// JsRequest.m
|
||||
// ScratchJr Free
|
||||
//
|
||||
// Created by Yueyu on 2020/7/31.
|
||||
//
|
||||
|
||||
#import "ScratchJr.h"
|
||||
|
||||
@implementation JsRequest
|
||||
|
||||
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
|
||||
self = [super init];
|
||||
self.method = dictionary[@"method"];
|
||||
self.callId = dictionary[@"id"];
|
||||
self.params = dictionary[@"params"];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void) callback:(NSString *)res {
|
||||
NSString *js = nil;
|
||||
if ([res hasPrefix:@"["] || [res hasPrefix:@"{"]) {
|
||||
js = [NSString stringWithFormat:@"iOS.resolve('%@', %@);", self.callId, res];
|
||||
} else {
|
||||
js = [NSString stringWithFormat:@"iOS.resolve('%@', '%@');", self.callId, res];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ViewController.webview evaluateJavaScript:js completionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
|
@ -3,6 +3,7 @@
|
|||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import <JavaScriptCore/JavaScriptCore.h>
|
||||
#import <WebKit/WebKit.h>
|
||||
|
||||
@interface Database : NSObject
|
||||
|
||||
|
@ -84,56 +85,8 @@
|
|||
+ (NSString *)stopPlay;
|
||||
+ (NSString *)recordclose:(NSString *)keep;
|
||||
@end
|
||||
|
||||
@protocol JSExports <JSExport>
|
||||
/* Functions exported to JavaScript */
|
||||
- (NSString *)hideSplash:(NSString *)body;
|
||||
- (void) askForPermission;
|
||||
- (NSString *)database_stmt:(NSString *) json;
|
||||
- (NSString *)database_query:(NSString *) json;
|
||||
- (NSString *)io_getmd5:(NSString *) str;
|
||||
- (NSString *)io_getsettings;
|
||||
- (void)io_cleanassets:(NSString *)fileType;
|
||||
- (NSString *)io_setfile:(NSString *)filename :(NSString *)base64ContentStr;
|
||||
- (NSString *)io_getfile:(NSString *)filename;
|
||||
- (NSString *)io_setmedia:(NSString *)base64ContentStr :(NSString *)extension;
|
||||
- (NSString *)io_setmedianame:(NSString *)contents :(NSString *)key :(NSString *)ext;
|
||||
- (NSString *)io_getmedia:(NSString *)filename;
|
||||
- (NSString *)io_getmediadata:(NSString *)filename :(int)offset :(int)length;
|
||||
- (NSString *)io_getmedialen:(NSString *)file :(NSString *)key;
|
||||
- (NSString *)io_getmediadone:(NSString *)filename;
|
||||
- (NSString *)io_remove:(NSString *)filename;
|
||||
- (NSString *)io_registersound:(NSString *)dir :(NSString *)name;
|
||||
- (NSString *)io_playsound:(NSString *)name;
|
||||
- (NSString *)io_stopsound:(NSString *)name;
|
||||
|
||||
- (NSString *)recordsound_recordstart;
|
||||
- (NSString *)recordsound_recordstop;
|
||||
- (NSString *)recordsound_volume;
|
||||
- (NSString *)recordsound_startplay;
|
||||
- (NSString *)recordsound_stopplay;
|
||||
- (NSString *)recordsound_recordclose:(NSString *)keep;
|
||||
|
||||
- (NSString *)scratchjr_cameracheck;
|
||||
- (bool) scratchjr_has_multiple_cameras;
|
||||
- (NSString *)scratchjr_startfeed:(NSString *)str;
|
||||
- (NSString *)scratchjr_stopfeed;
|
||||
- (NSString *)scratchjr_choosecamera:(NSString *)body;
|
||||
- (NSString *)scratchjr_captureimage:(NSString *)onCameraCaptureComplete;
|
||||
- (NSString *)sendSjrUsingShareDialog:(NSString *)fileName
|
||||
:(NSString *)emailSubject
|
||||
:(NSString *)emailBody
|
||||
:(int)shareType
|
||||
:(NSString *)b64data;
|
||||
- (NSString *) deviceName;
|
||||
- (NSString *) analyticsEvent:(NSString *)category :(NSString *)action :(NSString *)label;
|
||||
- (void) setAnalyticsPlacePref:(NSString *)place;
|
||||
- (void) setAnalyticsPref:(NSString *)prefObjStr;
|
||||
@end
|
||||
|
||||
@interface ViewController : UIViewController <JSExports,UIWebViewDelegate,MFMailComposeViewControllerDelegate>
|
||||
@property (nonatomic, readwrite, strong) JSContext *js;
|
||||
+ (UIWebView *)webview;
|
||||
@interface ViewController : UIViewController <MFMailComposeViewControllerDelegate, WKNavigationDelegate>
|
||||
+ (WKWebView *)webview;
|
||||
+ (UIImageView *)splashScreen;
|
||||
- (void)receiveProject:(NSString *)project;
|
||||
- (void)registerDefaultsFromSettingsBundle;
|
||||
|
@ -148,6 +101,23 @@
|
|||
- (void)showShareAirdrop:(NSURL *)projectURL;
|
||||
@end
|
||||
|
||||
@interface JsBridge: NSObject <WKScriptMessageHandler>
|
||||
|
||||
@property(weak, nonatomic) ViewController *controller;
|
||||
|
||||
@end
|
||||
|
||||
@interface JsRequest : NSObject
|
||||
|
||||
@property(nonatomic, readwrite) NSString* callId;
|
||||
@property(nonatomic, readwrite) NSString* method;
|
||||
@property(nonatomic, readwrite) NSArray* params;
|
||||
|
||||
- (instancetype) initWithDictionary: (NSDictionary *)dictionary;
|
||||
|
||||
- (void) callback: (NSString *) res;
|
||||
|
||||
@end
|
||||
|
||||
@interface IO : NSObject
|
||||
|
||||
|
|
|
@ -113,10 +113,10 @@ NSString *oncomplete;
|
|||
|
||||
+ (void)reportImageError {
|
||||
NSString *callback = [NSString stringWithFormat: @"%@('error getting a still');",oncomplete];
|
||||
UIWebView *webview = [ViewController webview];
|
||||
WKWebView *webview = [ViewController webview];
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webview stringByEvaluatingJavaScriptFromString: callback];
|
||||
[webview evaluateJavaScript:callback completionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -124,9 +124,9 @@ NSString *oncomplete;
|
|||
NSString *base64img = [cameraView getImageBase64:imagedata];
|
||||
[self closefeed];
|
||||
NSString *callback = [NSString stringWithFormat: @"%@('%@');",oncomplete, base64img];
|
||||
UIWebView *webview = [ViewController webview];
|
||||
WKWebView *webview = [ViewController webview];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webview stringByEvaluatingJavaScriptFromString: callback];
|
||||
[webview evaluateJavaScript:callback completionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
|
|
25
ios/ScratchJr/src/View.xib
Normal file
25
ios/ScratchJr/src/View.xib
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15705" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15706"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="ViewController">
|
||||
<connections>
|
||||
<outlet property="view" destination="iN0-l3-epB" id="sUQ-dS-ukS"/>
|
||||
</connections>
|
||||
</placeholder>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view clearsContextBeforeDrawing="NO" contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<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="vUN-kp-3ea"/>
|
||||
<point key="canvasLocation" x="139" y="103"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
|
@ -1,12 +1,12 @@
|
|||
#import "ScratchJr.h"
|
||||
#import <WebKit/WebKit.h>
|
||||
// @import MessageUI;
|
||||
@import Firebase;
|
||||
|
||||
UIWebView *webview;
|
||||
WKWebView *webview;
|
||||
NSDate* startDate;
|
||||
UIImageView *splashScreen;
|
||||
NSDate *startDate;
|
||||
JSContext *js;
|
||||
|
||||
@interface ViewController ()
|
||||
|
||||
|
@ -26,14 +26,17 @@ JSContext *js;
|
|||
- (void)viewDidLoad
|
||||
{
|
||||
[super viewDidLoad];
|
||||
|
||||
webview = [[WKWebView alloc] initWithFrame:CGRectZero configuration:[self webViewConfig]];
|
||||
webview.backgroundColor = UIColor.blackColor;
|
||||
webview.navigationDelegate = self;
|
||||
self.view = webview;
|
||||
[self registerDefaultsFromSettingsBundle];
|
||||
webview = (UIWebView*)[self view] ;
|
||||
|
||||
// disable webview scroll
|
||||
// to fix https://github.com/LLK/scratchjr/issues/243
|
||||
webview.scrollView.scrollEnabled = false;
|
||||
|
||||
[webview setDelegate:self];
|
||||
[Database open:@"ScratchJr"];
|
||||
[ScratchJr cameraInit];
|
||||
[self reload];
|
||||
|
@ -44,6 +47,18 @@ JSContext *js;
|
|||
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
|
||||
}
|
||||
|
||||
- (WKWebViewConfiguration*) webViewConfig {
|
||||
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
|
||||
config.allowsInlineMediaPlayback = true;
|
||||
config.allowsAirPlayForMediaPlayback = true;
|
||||
[config.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
|
||||
|
||||
WKUserContentController *controller = [[WKUserContentController alloc] init];
|
||||
[controller addScriptMessageHandler:[[JsBridge alloc] init] name:@"jsBridge"];
|
||||
config.userContentController = controller;
|
||||
return config;
|
||||
}
|
||||
|
||||
- (void) showSplash {
|
||||
UIImage *loadingImage = [UIImage imageNamed:@"Default-Landscape~ipad.png"];
|
||||
splashScreen = [[UIImageView alloc] initWithImage:loadingImage];
|
||||
|
@ -59,7 +74,7 @@ JSContext *js;
|
|||
// Dispose of any resources that can be recreated.
|
||||
}
|
||||
|
||||
+ (UIWebView*) webview {return webview;}
|
||||
+ (WKWebView*) webview {return webview;}
|
||||
+ (UIImageView*) splashScreen {return splashScreen;}
|
||||
|
||||
- (void)registerDefaultsFromSettingsBundle {
|
||||
|
@ -86,11 +101,11 @@ JSContext *js;
|
|||
}
|
||||
|
||||
- (void)reload {
|
||||
UIWebView *webview = (UIWebView*)[self view];
|
||||
WKWebView *webview = (WKWebView*)[self view];
|
||||
NSString *location = [[NSUserDefaults standardUserDefaults] stringForKey:@"html"];
|
||||
if ([location length] > 3) location = [location substringFromIndex:3];
|
||||
NSString *path = [[NSBundle mainBundle] pathForResource: @"HTML5/index" ofType:@"html"];
|
||||
NSURL *url = [NSURL URLWithString: [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
|
||||
NSURL *url = [NSURL fileURLWithPath:path];
|
||||
NSURLRequest* request = [NSURLRequest requestWithURL:url];
|
||||
[[NSURLCache sharedURLCache] removeAllCachedResponses];
|
||||
[webview loadRequest:request];
|
||||
|
@ -103,46 +118,10 @@ JSContext *js;
|
|||
startDate = [NSDate date];
|
||||
}
|
||||
|
||||
// UIWebView delegate methods
|
||||
|
||||
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
|
||||
//read your request here
|
||||
//before the webview will load your request
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)webViewDidStartLoad:(UIWebView *)webView{
|
||||
//access your request
|
||||
}
|
||||
|
||||
- (void)webViewDidFinishLoad:(UIWebView *)webView{
|
||||
// Inject a reference for the dispatch method into the UIWebView
|
||||
// This happens after the page is loaded and the page's onLoad method is called
|
||||
js = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
|
||||
js[@"tablet"] = self;
|
||||
[self disableWebViewLongPressGestures:webView];
|
||||
|
||||
NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"];
|
||||
|
||||
// Patch through app "advanced"->debug to allow users to display long-form errors
|
||||
if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) {
|
||||
[webView stringByEvaluatingJavaScriptFromString:@"window.reloadDebug = true;"];
|
||||
}
|
||||
|
||||
NSURL* screenName = webView.request.URL.filePathURL;
|
||||
NSString* screenString =[screenName absoluteString];
|
||||
NSArray<NSString*>* parts = [screenString componentsSeparatedByString:@"/"];
|
||||
NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject];
|
||||
|
||||
// Track pageview in Firebase?
|
||||
[FIRAnalytics setScreenName:page screenClass:NULL];
|
||||
|
||||
}
|
||||
|
||||
// Disables iOS 9 webview touch tooltip by disabling the long-press gesture recognizer in subviews
|
||||
// Thanks to Rye:
|
||||
// http://stackoverflow.com/questions/32687368/how-to-completely-disable-magnifying-glass-for-uiwebview-ios9
|
||||
- (void) disableWebViewLongPressGestures:(UIWebView *)webview {
|
||||
- (void) disableWebViewLongPressGestures:(WKWebView *)webview {
|
||||
for(id subView in webview.subviews) {
|
||||
if([subView isKindOfClass:[UIScrollView class]]) {
|
||||
UIScrollView *scrollView = (UIScrollView *)subView;
|
||||
|
@ -160,33 +139,27 @@ JSContext *js;
|
|||
}
|
||||
}
|
||||
|
||||
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
|
||||
NSLog(@"could not load the website caused by error DESC: %@", error);
|
||||
NSDictionary *userInfo = [error userInfo];
|
||||
NSString *desc = [NSString stringWithFormat:@"%@", ([userInfo objectForKey: @"NSLocalizedDescription"] == NULL)? [error localizedDescription]: [userInfo objectForKey: @"NSLocalizedDescription"]];
|
||||
NSString *callback = [NSString stringWithFormat: @"OS.pageError('%@');",desc];
|
||||
UIWebView *webview = [ViewController webview];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webview stringByEvaluatingJavaScriptFromString: callback];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) receiveProject:(NSString *)project{
|
||||
NSString *callback = [NSString stringWithFormat:@"OS.loadProjectFromSjr('%@');", project];
|
||||
UIWebView *webview = [ViewController webview];
|
||||
WKWebView *webview = [ViewController webview];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSString *res = [webview stringByEvaluatingJavaScriptFromString:callback];
|
||||
if ([res isEqualToString:@"1"]) {
|
||||
// Success
|
||||
return;
|
||||
} else if ([res isEqualToString:@"0"]) {
|
||||
// Processing error
|
||||
return;
|
||||
} else {
|
||||
// Loading the project failed - reschedule for a time when the WebView has hopefully loaded
|
||||
// A little bit roundabout, but simpler than queueing projects to be loaded
|
||||
[self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0];
|
||||
}
|
||||
[webview evaluateJavaScript:callback completionHandler:^(id result, NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
return;
|
||||
}
|
||||
NSString *res = [NSString stringWithFormat:@"%@", result];
|
||||
if ([res isEqualToString:@"1"]) {
|
||||
// Success
|
||||
return;
|
||||
} else if ([res isEqualToString:@"0"]) {
|
||||
// Processing error
|
||||
return;
|
||||
} else {
|
||||
// Loading the project failed - reschedule for a time when the WebView has hopefully loaded
|
||||
// A little bit roundabout, but simpler than queueing projects to be loaded
|
||||
[self performSelector:@selector(receiveProject:) withObject:project afterDelay:2.0];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -194,161 +167,39 @@ JSContext *js;
|
|||
return YES;
|
||||
}
|
||||
|
||||
/*
|
||||
* JavaScript Interface Exports
|
||||
*/
|
||||
// WKNavigationDelegate
|
||||
|
||||
-(void) askForPermission {
|
||||
[RecordSound setPermission];
|
||||
- (void) webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
|
||||
[webview evaluateJavaScript:@"window.tablet = window.webkit.messageHandlers.jsBridge" completionHandler:nil];
|
||||
[self disableWebViewLongPressGestures:webView];
|
||||
|
||||
NSString *debugChoice =[[NSUserDefaults standardUserDefaults] stringForKey:@"debugstate"];
|
||||
|
||||
// Patch through app "advanced"->debug to allow users to display long-form errors when projects fail to load
|
||||
if (![debugChoice isEqualToString:@""] && ![debugChoice isEqualToString:@"0"]) {
|
||||
[webView evaluateJavaScript:@"window.reloadDebug = true;" completionHandler:nil];
|
||||
}
|
||||
|
||||
NSURL* screenName = webView.URL.filePathURL;
|
||||
NSString* screenString =[screenName absoluteString];
|
||||
NSArray<NSString*>* parts = [screenString componentsSeparatedByString:@"/"];
|
||||
NSString* page = [[[parts lastObject] componentsSeparatedByString:@"?"] firstObject];
|
||||
|
||||
// Track pageview in Firebase?
|
||||
[FIRAnalytics setScreenName:page screenClass:NULL];
|
||||
}
|
||||
|
||||
-(NSString*) database_stmt: (NSString*) json {
|
||||
return [Database stmt:json];
|
||||
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
|
||||
NSLog(@"could not load the website caused by error DESC: %@", error);
|
||||
NSDictionary *userInfo = [error userInfo];
|
||||
NSString *desc = [NSString stringWithFormat:@"%@", ([userInfo objectForKey: @"NSLocalizedDescription"] == NULL)? [error localizedDescription]: [userInfo objectForKey: @"NSLocalizedDescription"]];
|
||||
NSString *callback = [NSString stringWithFormat: @"iOS.pageError('%@');",desc];
|
||||
WKWebView *webview = [ViewController webview];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[webview evaluateJavaScript:callback completionHandler:nil];
|
||||
});
|
||||
}
|
||||
|
||||
-(NSString*) database_query: (NSString*) json {
|
||||
return [Database query:json];
|
||||
}
|
||||
|
||||
-(NSString*) io_getmd5: (NSString*) str {
|
||||
return [IO getMD5:str];
|
||||
}
|
||||
|
||||
-(NSString*) io_getsettings {
|
||||
return [IO getsettings];
|
||||
}
|
||||
|
||||
-(void) io_cleanassets:(NSString*) fileType {
|
||||
[IO cleanassets:fileType];
|
||||
}
|
||||
|
||||
-(NSString*) io_setfile:(NSString*)filename :(NSString*)base64ContentStr {
|
||||
return [IO setfile:filename:base64ContentStr];
|
||||
}
|
||||
|
||||
-(NSString*) io_getfile:(NSString*)filename {
|
||||
return [IO getfile:filename];
|
||||
}
|
||||
|
||||
-(NSString*) io_setmedia:(NSString*) base64ContentStr :(NSString*) extension {
|
||||
return [IO setmedia:base64ContentStr:extension];
|
||||
}
|
||||
|
||||
-(NSString*) io_setmedianame:(NSString*) contents :(NSString*) key :(NSString*) ext {
|
||||
return [IO setmedianame:contents:key:ext];
|
||||
}
|
||||
|
||||
-(NSString*) io_getmedia:(NSString*) filename {
|
||||
return [IO getmedia:filename];
|
||||
}
|
||||
|
||||
-(NSString*) io_getmediadata:(NSString*)filename :(int) offset :(int) length {
|
||||
return [IO getmediadata:filename:offset:length];
|
||||
}
|
||||
|
||||
-(NSString*) io_getmedialen:(NSString*)file :(NSString*)key {
|
||||
return [IO getmedialen:file:key];
|
||||
}
|
||||
|
||||
-(NSString*) io_getmediadone:(NSString*)filename {
|
||||
return [IO getmediadone:filename];
|
||||
}
|
||||
|
||||
-(NSString*) io_remove:(NSString*)filename {
|
||||
return [IO remove:filename];
|
||||
}
|
||||
|
||||
-(NSString*) io_registersound:(NSString*)dir :(NSString*)name {
|
||||
return [IO registerSound:dir:name];
|
||||
}
|
||||
|
||||
-(NSString*) io_playsound:(NSString*) name {
|
||||
return [IO playSound:name];
|
||||
}
|
||||
|
||||
-(NSString*) io_stopsound:(NSString*) name {
|
||||
return [IO stopSound:name];
|
||||
}
|
||||
|
||||
-(NSString*) recordsound_recordstart {
|
||||
return [RecordSound startRecord];
|
||||
}
|
||||
-(NSString*) recordsound_recordstop {
|
||||
return [RecordSound stopRecording];
|
||||
}
|
||||
-(NSString*) recordsound_volume {
|
||||
return [NSString stringWithFormat:@"%f", [RecordSound getVolume]];
|
||||
}
|
||||
-(NSString*) recordsound_startplay {
|
||||
return [RecordSound startPlay];
|
||||
}
|
||||
-(NSString*) recordsound_stopplay {
|
||||
return [RecordSound stopPlay];
|
||||
}
|
||||
-(NSString*) recordsound_recordclose:(NSString*) keep {
|
||||
return [RecordSound recordclose:keep];
|
||||
}
|
||||
|
||||
-(NSString*) scratchjr_cameracheck {
|
||||
return [ScratchJr cameracheck];
|
||||
}
|
||||
-(bool) scratchjr_has_multiple_cameras {
|
||||
return YES;
|
||||
}
|
||||
-(NSString*) scratchjr_startfeed:(NSString*)str {
|
||||
return [ScratchJr startfeed:str];
|
||||
}
|
||||
-(NSString*) scratchjr_stopfeed {
|
||||
return [ScratchJr stopfeed];
|
||||
}
|
||||
|
||||
-(NSString*) scratchjr_choosecamera:(NSString *)body {
|
||||
return [ScratchJr choosecamera:body];
|
||||
}
|
||||
|
||||
-(NSString*) scratchjr_captureimage: (NSString*)onCameraCaptureComplete {
|
||||
return [ScratchJr captureimage:onCameraCaptureComplete];
|
||||
}
|
||||
|
||||
//OS.sendSjrToShareDialog = function(fileName, emailSubject, emailBody, shareType, b64data) {
|
||||
|
||||
-(NSString*) sendSjrUsingShareDialog:(NSString*) fileName :(NSString*) emailSubject :(NSString*) emailBody :(int) shareType :(NSString*) b64data {
|
||||
return [IO sendSjrUsingShareDialog:fileName :emailSubject :emailBody :shareType : b64data];
|
||||
}
|
||||
|
||||
- (NSString *) hideSplash :(NSString *)body {
|
||||
return [ScratchJr hideSplash:body];
|
||||
}
|
||||
|
||||
-(NSString*) analyticsEvent:(NSString*) category :(NSString*) action :(NSString*) label {
|
||||
[FIRAnalytics logEventWithName:action
|
||||
parameters:@{
|
||||
kFIRParameterItemName:label,
|
||||
kFIRParameterItemCategory:category
|
||||
}];
|
||||
return @"1";
|
||||
}
|
||||
|
||||
-(void) setAnalyticsPlacePref:(NSString*)place {
|
||||
[FIRAnalytics setUserPropertyString:place forName:@"place_preference"];
|
||||
}
|
||||
|
||||
// @param prefObjStr like "{\"place_preference\": \"School\"}"
|
||||
-(void) setAnalyticsPref:(NSString*)prefObjStr {
|
||||
NSData* data = [prefObjStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSDictionary* dict = [NSJSONSerialization JSONObjectWithData:data options:0 error: nil];
|
||||
NSString *key = [[dict allKeys] firstObject];
|
||||
NSString *value = [dict objectForKey: key];
|
||||
[FIRAnalytics setUserPropertyString:value forName:key];
|
||||
}
|
||||
|
||||
|
||||
// iPad name (used for information in the name/sharing dialog to help people using Airdrop)
|
||||
- (NSString*) deviceName {
|
||||
return [[UIDevice currentDevice] name];
|
||||
}
|
||||
|
||||
|
||||
// Sharing controllers - if we later decide to unify, use UIActivityViewController
|
||||
|
||||
// Email sharing
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue