From 170e11ba2351b20baeaedb70a97ff3a004fe12c3 Mon Sep 17 00:00:00 2001 From: robaho Date: Thu, 21 Oct 2021 19:52:20 -0500 Subject: [PATCH] example/ivy/ios: enable demo functionality And bring iOS app up to Android capabilities. Fixes golang/go#48694 Change-Id: I0e853cab102e34053ecdb17edb99644aa2f8651a Reviewed-on: https://go-review.googlesource.com/c/mobile/+/357977 Reviewed-by: Hyang-Ah Hana Kim Trust: Hyang-Ah Hana Kim Trust: Peter Weinberger --- example/ivy/ios/README.md | 2 +- example/ivy/ios/ivy.xcodeproj/project.pbxproj | 15 +- .../ivy/ios/ivy/Base.lproj/Main.storyboard | 77 ++++-- .../AppIcon.appiconset/Contents.json | 28 +++ example/ivy/ios/ivy/Info.plist | 2 +- example/ivy/ios/ivy/IvyController.h | 9 +- example/ivy/ios/ivy/IvyController.m | 219 +++++++++++------- example/ivy/ios/ivy/tape.html | 76 +++--- 8 files changed, 292 insertions(+), 136 deletions(-) diff --git a/example/ivy/ios/README.md b/example/ivy/ios/README.md index 453ccbc9c..bfbaa1998 100644 --- a/example/ivy/ios/README.md +++ b/example/ivy/ios/README.md @@ -17,7 +17,7 @@ mkdir work; cd work go mod init work go get -d golang.org/x/mobile/bind@latest go get -d robpike.io/ivy/mobile -gomobile bind -target=ios,iossimulator,maccatalyst,macos robpike.io/ivy/mobile +gomobile bind -target=ios,iossimulator,maccatalyst,macos robpike.io/ivy/mobile robpike.io/ivy/demo ``` Place the Mobile.xcframework directory in this directory, and diff --git a/example/ivy/ios/ivy.xcodeproj/project.pbxproj b/example/ivy/ios/ivy.xcodeproj/project.pbxproj index aeb1d309a..f18308579 100644 --- a/example/ivy/ios/ivy.xcodeproj/project.pbxproj +++ b/example/ivy/ios/ivy.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -316,12 +316,16 @@ ); INFOPLIST_FILE = ivy/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); ONLY_ACTIVE_ARCH = NO; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = ivy; STRIP_STYLE = debugging; SUPPORTS_MACCATALYST = YES; @@ -342,13 +346,16 @@ ); INFOPLIST_FILE = ivy/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", ); ONLY_ACTIVE_ARCH = NO; - PRODUCT_BUNDLE_IDENTIFIER = "com.google.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "com.google.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = ivy; STRIP_STYLE = debugging; SUPPORTS_MACCATALYST = YES; diff --git a/example/ivy/ios/ivy/Base.lproj/Main.storyboard b/example/ivy/ios/ivy/Base.lproj/Main.storyboard index db250c15c..18a65a958 100644 --- a/example/ivy/ios/ivy/Base.lproj/Main.storyboard +++ b/example/ivy/ios/ivy/Base.lproj/Main.storyboard @@ -1,10 +1,10 @@ - + - + @@ -55,15 +55,38 @@ - - - - - - - + + + + + + + + + + + + + + + + + + + - + @@ -73,29 +96,44 @@ - - + + + - - + - + - + + + + + + + + + + + + + - + + + + - + @@ -130,4 +168,7 @@ + + + diff --git a/example/ivy/ios/ivy/Images.xcassets/AppIcon.appiconset/Contents.json b/example/ivy/ios/ivy/Images.xcassets/AppIcon.appiconset/Contents.json index 466150b7d..b6cf3532e 100644 --- a/example/ivy/ios/ivy/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/example/ivy/ios/ivy/Images.xcassets/AppIcon.appiconset/Contents.json @@ -150,6 +150,13 @@ "scale" : "3x", "size" : "29x29" }, + { + "idiom" : "watch", + "role" : "notificationCenter", + "scale" : "2x", + "size" : "33x33", + "subtype" : "45mm" + }, { "filename" : "icon-80.png", "idiom" : "watch", @@ -166,6 +173,13 @@ "size" : "44x44", "subtype" : "40mm" }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "46x46", + "subtype" : "41mm" + }, { "filename" : "icon-100.png", "idiom" : "watch", @@ -174,6 +188,13 @@ "size" : "50x50", "subtype" : "44mm" }, + { + "idiom" : "watch", + "role" : "appLauncher", + "scale" : "2x", + "size" : "51x51", + "subtype" : "45mm" + }, { "filename" : "icon-172.png", "idiom" : "watch", @@ -198,6 +219,13 @@ "size" : "108x108", "subtype" : "44mm" }, + { + "idiom" : "watch", + "role" : "quickLook", + "scale" : "2x", + "size" : "117x117", + "subtype" : "45mm" + }, { "filename" : "icon-1024.png", "idiom" : "watch-marketing", diff --git a/example/ivy/ios/ivy/Info.plist b/example/ivy/ios/ivy/Info.plist index aad490518..9438af623 100644 --- a/example/ivy/ios/ivy/Info.plist +++ b/example/ivy/ios/ivy/Info.plist @@ -11,7 +11,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) + com.google.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/example/ivy/ios/ivy/IvyController.h b/example/ivy/ios/ivy/IvyController.h index 0d78d9ca9..e76bf2f13 100644 --- a/example/ivy/ios/ivy/IvyController.h +++ b/example/ivy/ios/ivy/IvyController.h @@ -14,8 +14,13 @@ @property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint; // A text input field coupled to an output "tape", rendered with a WKWebView. -@property (strong, nonatomic) UITextField *input; +@property (weak, nonatomic) IBOutlet UITextField *input; @property (strong, nonatomic) Suggestion *suggestionView; -@property (strong, nonatomic) WKWebView *tape; +@property (weak, nonatomic) IBOutlet WKWebView *tape; +@property (weak, nonatomic) IBOutlet UIButton *okButton; + +- (IBAction)clear:(id)sender; +- (IBAction)demo:(id)sender; +- (IBAction)okPressed:(id)sender; @end diff --git a/example/ivy/ios/ivy/IvyController.m b/example/ivy/ios/ivy/IvyController.m index 42e4dd7c8..ecac33fb4 100644 --- a/example/ivy/ios/ivy/IvyController.m +++ b/example/ivy/ios/ivy/IvyController.m @@ -9,50 +9,46 @@ @interface IvyController () @end -@implementation IvyController +@implementation IvyController { + NSArray *demo_lines; + int demo_index; +} - (void)viewDidLoad { [super viewDidLoad]; - - self.input = (UITextField *)[self.view viewWithTag:1]; + self.input.delegate = self; self.input.autocorrectionType = UITextAutocorrectionTypeNo; self.input.keyboardType = UIKeyboardTypeNumbersAndPunctuation; - + self.suggestionView = [[Suggestion alloc] init]; self.suggestionView.delegate = self; - - self.tape = [self.view viewWithTag:2]; + self.tape.UIDelegate = self; - + self->demo_lines=NULL; + + [self.okButton setTitle:@"" forState:UIControlStateNormal]; + [self.okButton setHidden:TRUE]; + [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(textDidChange:) - name:UITextFieldTextDidChangeNotification - object:self.input]; + addObserver:self + selector:@selector(textDidChange:) + name:UITextFieldTextDidChangeNotification + object:self.input]; [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(keyboardWillShow:) - name:UIKeyboardWillShowNotification - object:nil]; + addObserver:self + selector:@selector(keyboardWillShow:) + name:UIKeyboardWillShowNotification + object:nil]; [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(keyboardWillHide:) - name:UIKeyboardWillHideNotification - object:nil]; - - NSURL *bundleURL = - [[NSBundle mainBundle] URLForResource:@"tape" withExtension:@"html"]; - NSURLRequest *request = [NSURLRequest requestWithURL:bundleURL]; - [self.tape loadRequest:request]; - self.tape.UIDelegate = self; + addObserver:self + selector:@selector(keyboardWillHide:) + name:UIKeyboardWillHideNotification + object:nil]; + [self.input becomeFirstResponder]; -} - -- (void)viewDidAppear:(BOOL)animated -{ - [self.view endEditing:YES]; + [self clear:NULL]; } - (BOOL)textFieldShouldBeginEditing:(UITextField *)textField @@ -75,33 +71,14 @@ - (BOOL)textFieldShouldEndEditing:(UITextField *)textField } - (BOOL)textField:(UITextField *)textField - shouldChangeCharactersInRange:(NSRange)range - replacementString:(NSString *)str +shouldChangeCharactersInRange:(NSRange)range +replacementString:(NSString *)str { if ([str isEqualToString:@"\n"]) { - [self - appendTape:[NSString stringWithFormat:@"%@", [self.input text]]]; - NSString *expr = [self.input.text stringByAppendingString:@"\n"]; - NSError *err; - NSString *result = MobileEval(expr, &err); - if (err != nil) { - result = err.description; - } - result = [result - stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - result = - [result stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; - result = - [result stringByReplacingOccurrencesOfString:@">" withString:@">"]; - NSMutableArray *lines = - (NSMutableArray *)[result componentsSeparatedByString:@"\n"]; - for (NSMutableString *line in lines) { - [self appendTape:line]; - } - self.input.text = @""; + [self enterPressed]; return NO; } - + return YES; } @@ -121,51 +98,98 @@ - (void)keyboardWillShow:(NSNotification *)aNotification // Move the input text field up, as the keyboard has taken some of the screen. NSDictionary *info = [aNotification userInfo]; CGRect kbFrame = - [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; + [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]; NSNumber *duration = - [info objectForKey:UIKeyboardAnimationDurationUserInfoKey]; - + [info objectForKey:UIKeyboardAnimationDurationUserInfoKey]; + UIViewAnimationCurve keyboardTransitionAnimationCurve; [[info valueForKey:UIKeyboardAnimationCurveUserInfoKey] - getValue:&keyboardTransitionAnimationCurve]; + getValue:&keyboardTransitionAnimationCurve]; UIViewAnimationOptions options = - keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16; - + keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16; + [UIView animateWithDuration:duration.floatValue - delay:0 - options:options - animations:^{ - self.bottomConstraint.constant = kbFrame.size.height; + delay:0 + options:options + animations:^{ + self.bottomConstraint.constant = 0 - kbFrame.size.height; [self.view layoutIfNeeded]; - } - completion:^(BOOL finished) { + } + completion:^(BOOL finished) { [self scrollTapeToBottom]; - }]; + }]; } - (void)keyboardWillHide:(NSNotification *)aNotification { // Move the input text field back down. NSDictionary *info = [aNotification userInfo]; + NSNumber *duration = - [info objectForKey:UIKeyboardAnimationDurationUserInfoKey]; + [info objectForKey:UIKeyboardAnimationDurationUserInfoKey]; UIViewAnimationCurve keyboardTransitionAnimationCurve; [[info valueForKey:UIKeyboardAnimationCurveUserInfoKey] - getValue:&keyboardTransitionAnimationCurve]; + getValue:&keyboardTransitionAnimationCurve]; UIViewAnimationOptions options = - keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16; - + keyboardTransitionAnimationCurve | keyboardTransitionAnimationCurve << 16; + + int offset = self.input.inputAccessoryView != NULL ? self.suggestionView.frame.size.height : 0; + [UIView animateWithDuration:duration.floatValue - delay:0 - options:options - animations:^{ - self.bottomConstraint.constant = 32; + delay:0 + options:options + animations:^{ + self.bottomConstraint.constant = 0 - offset; [self.view layoutIfNeeded]; - } - completion:^(BOOL finished) { + } + completion:^(BOOL finished) { [self scrollTapeToBottom]; - }]; + }]; +} + +- (void)enterPressed +{ + NSString *text = self.input.text; + if ([text isEqual:@""]){ + if(self->demo_lines==NULL){ + return; + } + while (demo_index < self->demo_lines.count) { + NSString *line = self->demo_lines[self->demo_index++]; + if([line hasPrefix:@"#"]) { + [self appendTape:line tag:@"comment"]; + } else { + self.input.text = line; + break; + } + } + } else if (self->demo_lines!=NULL && [text isEqual:@"quit"]) { + [self unloadDemo]; + } else { + [self appendTape:text tag:@"expr"]; + NSString *expr = [text stringByAppendingString:@"\n"]; + NSError *err; + NSString *result = MobileEval(expr, &err); + if (err != nil) { + result = err.description; + } + result = [result + stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + result = + [result stringByReplacingOccurrencesOfString:@"<" withString:@"<"]; + result = + [result stringByReplacingOccurrencesOfString:@">" withString:@">"]; + NSMutableArray *lines = + (NSMutableArray *)[result componentsSeparatedByString:@"\n"]; + for (NSMutableString *line in lines) { + if ([line hasPrefix:@"#"]) + [self appendTape:line tag:@"comment"]; + else + [self appendTape:line tag:@"result"]; + } + self.input.text = @""; + } } - (void)scrollTapeToBottom @@ -174,11 +198,46 @@ - (void)scrollTapeToBottom [self.tape evaluateJavaScript:scroll completionHandler:nil]; } -- (void)appendTape:(NSString *)text +- (void)appendTape:(NSString *)text tag:(NSString *)tag { - NSString *injectSrc = @"appendDiv('%@');"; - NSString *runToInject = [NSString stringWithFormat:injectSrc, text]; + NSString *injectSrc = @"appendDiv('%@','%@');"; + NSString *runToInject = [NSString stringWithFormat:injectSrc, text, tag]; [self.tape evaluateJavaScript:runToInject completionHandler:nil]; + [self scrollTapeToBottom]; +} + +- (void)loadDemo +{ + [self.okButton setHidden:FALSE]; + NSString *text = DemoText(); + + self->demo_lines = [text componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + self->demo_index = 0; + self.input.text = @""; + [self enterPressed]; +} +- (void)unloadDemo +{ + [self.okButton setHidden:TRUE]; + self->demo_lines=NULL; + self.input.text = @""; +} +- (IBAction)okPressed:(id)sender { + [self enterPressed]; } +- (IBAction)demo:(id)sender { + if (self->demo_lines) { // demo already running + [self enterPressed]; + } else { + [self loadDemo]; + } +} + +- (IBAction)clear:(id)sender { + [self unloadDemo]; + NSString *string = [NSString stringWithContentsOfFile:[[NSBundle mainBundle] + pathForResource:@"tape" ofType:@"html"] encoding:NSUTF8StringEncoding error:NULL]; + [self.tape loadHTMLString:string baseURL:NULL]; +} @end diff --git a/example/ivy/ios/ivy/tape.html b/example/ivy/ios/ivy/tape.html index 4b9c796be..7340e908d 100644 --- a/example/ivy/ios/ivy/tape.html +++ b/example/ivy/ios/ivy/tape.html @@ -5,40 +5,56 @@ --> - - -.flowhide { - text-overflow: ellipsis; - word-break: break-word; - overflow-wrap: break-word; -} +