From 7a0f0794aa24f15474c1f04f13412e0a0da7e96f Mon Sep 17 00:00:00 2001
From: Tim Oliver <me@timoliver.com.au>
Date: Wed, 6 Apr 2016 20:49:52 +0800
Subject: [PATCH] Added items class and gesture recognisers.

---
 TOWebViewController/TOWebHistoryitem.h        | 45 ++++++++++
 TOWebViewController/TOWebHistoryitem.m        | 71 ++++++++++++++++
 TOWebViewController/TOWebViewController.h     |  8 ++
 TOWebViewController/TOWebViewController.m     | 85 +++++++++++++++++--
 .../project.pbxproj                           | 14 +++
 5 files changed, 216 insertions(+), 7 deletions(-)
 create mode 100644 TOWebViewController/TOWebHistoryitem.h
 create mode 100644 TOWebViewController/TOWebHistoryitem.m

diff --git a/TOWebViewController/TOWebHistoryitem.h b/TOWebViewController/TOWebHistoryitem.h
new file mode 100644
index 0000000..1224f8d
--- /dev/null
+++ b/TOWebViewController/TOWebHistoryitem.h
@@ -0,0 +1,45 @@
+//
+//  TOWebHistoryitem.h
+//
+//  Copyright 2016 Timothy Oliver. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to
+//  deal in the Software without restriction, including without limitation the
+//  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+//  sell copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+//  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+#import <Foundation/Foundation.h>
+
+/**
+ `TOWebHistoryitem` represents a page in the history of pages navigated in this session
+*/
+@interface TOWebHistoryitem : NSObject
+
+/**
+ The string from the <title> tag of this particular page
+ */
+@property (nonatomic,readonly) NSString *title;
+
+/**
+ The URL of this web page
+ */
+@property (nonatomic,readonly) NSURL *URL;
+
+/**
+ Class instantiation method
+ */
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)URL;
+
+@end
diff --git a/TOWebViewController/TOWebHistoryitem.m b/TOWebViewController/TOWebHistoryitem.m
new file mode 100644
index 0000000..1524fa9
--- /dev/null
+++ b/TOWebViewController/TOWebHistoryitem.m
@@ -0,0 +1,71 @@
+//
+//  TOWebHistoryitem.m
+//
+//  Copyright 2016 Timothy Oliver. All rights reserved.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to
+//  deal in the Software without restriction, including without limitation the
+//  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+//  sell copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in
+//  all copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+//  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+//  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+#import "TOWebHistoryitem.h"
+
+@interface TOWebHistoryitem ()
+
+@property (nonatomic,copy,readwrite) NSString *title;
+@property (nonatomic,copy,readwrite) NSURL *URL;
+
+@end
+
+@implementation TOWebHistoryitem
+
+- (instancetype)initWithTitle:(NSString *)title URL:(NSURL *)URL
+{
+    if (self = [super init]) {
+        _title = title;
+        _URL = URL;
+    }
+    
+    return self;
+}
+
+- (BOOL)isEqual:(id)object
+{
+    if (self == object) {
+        return YES;
+    }
+    
+    if (![object isKindOfClass:[TOWebHistoryitem class]]) {
+        return NO;
+    }
+    
+    if ([self.title isEqualToString:[object title]] == NO) {
+        return NO;
+    }
+    
+    if ([self.URL isEqual:[object URL]] == NO) {
+        return NO;
+    }
+    
+    return YES;
+}
+
+- (NSUInteger)hash
+{
+    return self.title.hash ^ self.URL.hash;
+}
+
+@end
diff --git a/TOWebViewController/TOWebViewController.h b/TOWebViewController/TOWebViewController.h
index e6a9a43..e6a414a 100755
--- a/TOWebViewController/TOWebViewController.h
+++ b/TOWebViewController/TOWebViewController.h
@@ -156,6 +156,14 @@
  */
 @property (nonatomic,assign)    BOOL hideWebViewBoundaries;
 
+/**
+ When set to YES, this disables the popup menus that appear when the user
+ taps and holds on the back or forward buttons.
+ 
+ Default value is NO.
+ */
+@property (nonatomic,assign)    BOOL historyPopupsDisabled;
+
 /** 
  When the view controller is being presented as a modal popup, this block will be automatically performed
  right after the view controller is dismissed.
diff --git a/TOWebViewController/TOWebViewController.m b/TOWebViewController/TOWebViewController.m
index 45ba341..4c1c6f4 100755
--- a/TOWebViewController/TOWebViewController.m
+++ b/TOWebViewController/TOWebViewController.m
@@ -102,6 +102,14 @@ @interface TOWebViewController () <UIActionSheetDelegate,
 @property (nonatomic,strong) UIBarButtonItem *actionButton;           /* Shows the UIActivityViewController */
 @property (nonatomic,strong) UIBarButtonItem *doneButton;             /* The 'Done' button for modal contorllers */
 
+/* Back and Forward Button Gesture Recognizers */
+@property (nonatomic,strong) UILongPressGestureRecognizer *backButtonLongPressGestureRecognizer;
+@property (nonatomic,strong) UILongPressGestureRecognizer *forwardButtonLongPressGestureRecognizer;
+
+/* History tracking */
+@property (nonatomic,strong) NSMutableArray *backHistoryItems;
+@property (nonatomic,strong) NSMutableArray *forwardHistoryItems;
+
 /* Load Progress Manager */
 @property (nonatomic,strong) NJKWebViewProgress *progressManager;
 
@@ -116,6 +124,7 @@ @interface TOWebViewController () <UIActionSheetDelegate,
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 @property (nonatomic,strong) UIPopoverController *sharingPopoverController;
+@property (nonatomic,strong) UIPopoverController *historyPopoverController;
 #pragma GCC diagnostic pop
 
 /* See if we need to revert the toolbar to 'hidden' when we pop off a navigation controller. */
@@ -134,6 +143,7 @@ - (NSURL *)cleanURL:(NSURL *)url;
 
 /* Init and configure various sections of the controller */
 - (void)setUpNavigationButtons;
+- (void)setUpNavigationButtonGestureRecognizers;
 
 /* Review the current state of the web view and update the UI controls in the nav bar to match it */
 - (void)refreshButtonsState;
@@ -147,6 +157,9 @@ - (void)reloadStopButtonTapped:(id)sender;
 - (void)actionButtonTapped:(id)sender;
 - (void)doneButtonTapped:(id)sender;
 
+/* Gesture Recognizer Callbacks */
+- (void)longPressGestureRecognized:(UILongPressGestureRecognizer *)gestureRecognizer;
+
 /* Event handlers for items in the 'action' popup */
 - (void)copyURLToClipboard;
 - (void)openInBrowser;
@@ -274,8 +287,9 @@ - (void)loadView
     
     
     //only load the buttons if we need to
-    if (self.navigationButtonsHidden == NO)
+    if (self.navigationButtonsHidden == NO) {
         [self setUpNavigationButtons];
+    }
 }
 
 - (void)setUpNavigationButtons
@@ -315,6 +329,34 @@ - (void)setUpNavigationButtons
     }
 }
 
+- (void)setUpNavigationButtonGestureRecognizers
+{
+    if (self.navigationButtonsHidden || self.historyPopupsDisabled) {
+        return;
+    }
+    
+    if (self.backButtonLongPressGestureRecognizer && self.forwardButtonLongPressGestureRecognizer) {
+        return;
+    }
+    
+    if (self.backButtonLongPressGestureRecognizer == nil) {
+        self.backButtonLongPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureRecognized:)];
+    }
+    UIView *backButtonView = [self.backButton valueForKey:@"view"];
+    if (backButtonView.gestureRecognizers.count == 0) {
+        [backButtonView addGestureRecognizer:self.backButtonLongPressGestureRecognizer];
+    }
+    
+    if (self.forwardButtonLongPressGestureRecognizer == nil) {
+        self.forwardButtonLongPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGestureRecognized:)];
+    }
+    
+    UIView *forwardButtonView = [self.forwardButton valueForKey:@"view"];
+    if (forwardButtonView.gestureRecognizers.count == 0) {
+        [forwardButtonView addGestureRecognizer:self.forwardButtonLongPressGestureRecognizer];
+    }
+}
+
 - (void)viewDidLoad
 {
     [super viewDidLoad];
@@ -348,6 +390,11 @@ - (void)viewDidLoad
         
         self.doneButton.tintColor = self.buttonTintColor;
     }
+    
+    if (self.historyPopupsDisabled == NO) {
+        self.backHistoryItems = [NSMutableArray array];
+        self.forwardHistoryItems = [NSMutableArray array];
+    }
 }
 
 #pragma mark - View Presentation/Dismissal -
@@ -387,6 +434,8 @@ - (void)viewDidAppear:(BOOL)animated
         [self.urlRequest setURL:self.url];
         [self.webView loadRequest:self.urlRequest];
     }
+    
+    [self setUpNavigationButtonGestureRecognizers];
 }
 
 - (void)viewWillDisappear:(BOOL)animated
@@ -578,7 +627,6 @@ - (void)layoutButtonsForCurrentSizeClass
         }
         
         self.toolbarItems = items;
-        
         return;
     }
     
@@ -779,6 +827,23 @@ - (void)setApplicationLeftBarButtonItems:(NSArray *)applicationLeftBarButtonItem
     [self refreshButtonsState];
 }
 
+- (void)setHistoryPopupsDisabled:(BOOL)disableHistoryPopups
+{
+    if (self.historyPopupsDisabled == disableHistoryPopups) {
+        return;
+    }
+    
+    _historyPopupsDisabled = disableHistoryPopups;
+    
+    if (_historyPopupsDisabled == YES) {
+        [self.backButtonLongPressGestureRecognizer.view removeGestureRecognizer:self.backButtonLongPressGestureRecognizer];
+        [self.forwardButtonLongPressGestureRecognizer.view removeGestureRecognizer:self.forwardButtonLongPressGestureRecognizer];
+    }
+    else {
+        [self setUpNavigationButtonGestureRecognizers];
+    }
+}
+
 #pragma mark -
 #pragma mark WebView Delegate
 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
@@ -823,11 +888,10 @@ -(void)webViewProgress:(NJKWebViewProgress *)webViewProgress updateProgress:(flo
     if (interactive || complete)
     {
         //see if we can set the proper page title yet
-        if (self.showPageTitles) {
-            NSString *title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];
-            
-            if (title.length)
-                self.title = title;
+        NSString *title = [self.webView stringByEvaluatingJavaScriptFromString:@"document.title"];
+        
+        if (title.length && self.showPageTitles) {
+            self.title = title;
         }
         
         //if we're matching the view BG to the web view, update the background colour now
@@ -1176,6 +1240,13 @@ - (void)openTwitterDialog
 #pragma clang diagnostic pop
 }
 
+- (void)longPressGestureRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
+{
+    if (gestureRecognizer.state != UIGestureRecognizerStateBegan) {
+        return;
+    }
+}
+
 #pragma mark -
 #pragma mark UIWebView Attrbutes
 - (UIView *)webViewContentView
diff --git a/TOWebViewControllerExample.xcodeproj/project.pbxproj b/TOWebViewControllerExample.xcodeproj/project.pbxproj
index 32ae57d..b56c2ad 100644
--- a/TOWebViewControllerExample.xcodeproj/project.pbxproj
+++ b/TOWebViewControllerExample.xcodeproj/project.pbxproj
@@ -11,6 +11,11 @@
 		227C55F91750F1F800FC3411 /* TOAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 225014F21737B87600DF6D7E /* TOAppDelegate.m */; };
 		227C55FA1750F1F900FC3411 /* TOViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 225014F41737B87600DF6D7E /* TOViewController.m */; };
 		227C55FB1750F20700FC3411 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 225014FE1737B8A200DF6D7E /* main.m */; };
+		227D20A91CB52411008F641B /* TOWebHistoryitem.h in Headers */ = {isa = PBXBuildFile; fileRef = 227D20A71CB52411008F641B /* TOWebHistoryitem.h */; };
+		227D20AA1CB52411008F641B /* TOWebHistoryitem.m in Sources */ = {isa = PBXBuildFile; fileRef = 227D20A81CB52411008F641B /* TOWebHistoryitem.m */; };
+		227D20AB1CB52411008F641B /* TOWebHistoryitem.m in Sources */ = {isa = PBXBuildFile; fileRef = 227D20A81CB52411008F641B /* TOWebHistoryitem.m */; };
+		227D20AC1CB52411008F641B /* TOWebHistoryitem.m in Sources */ = {isa = PBXBuildFile; fileRef = 227D20A81CB52411008F641B /* TOWebHistoryitem.m */; };
+		227D20AD1CB52411008F641B /* TOWebHistoryitem.m in Sources */ = {isa = PBXBuildFile; fileRef = 227D20A81CB52411008F641B /* TOWebHistoryitem.m */; };
 		2282AEEC1B2E473900BD92FC /* TOWebViewControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2282AEEB1B2E473900BD92FC /* TOWebViewControllerTests.m */; };
 		2282E1D41C266D5600D53AB1 /* TOActivityChrome.m in Sources */ = {isa = PBXBuildFile; fileRef = 22AC285618E9920B006DB0E9 /* TOActivityChrome.m */; };
 		2282E1D51C266D5600D53AB1 /* TOViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 225014F41737B87600DF6D7E /* TOViewController.m */; };
@@ -93,6 +98,8 @@
 		225014FF1737B8A200DF6D7E /* DefaultExample-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "DefaultExample-Info.plist"; path = "Example/DefaultExample-Info.plist"; sourceTree = SOURCE_ROOT; };
 		225015001737B8A200DF6D7E /* TOWebViewControllerExample-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "TOWebViewControllerExample-Prefix.pch"; path = "Example/TOWebViewControllerExample-Prefix.pch"; sourceTree = SOURCE_ROOT; };
 		227B3CEE1BAECF95009AC60E /* LaunchImages.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = LaunchImages.xcassets; path = Example/LaunchImages.xcassets; sourceTree = SOURCE_ROOT; };
+		227D20A71CB52411008F641B /* TOWebHistoryitem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TOWebHistoryitem.h; sourceTree = "<group>"; };
+		227D20A81CB52411008F641B /* TOWebHistoryitem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TOWebHistoryitem.m; sourceTree = "<group>"; };
 		2282AEE71B2E473900BD92FC /* TOWebViewControllerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TOWebViewControllerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		2282AEEA1B2E473900BD92FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		2282AEEB1B2E473900BD92FC /* TOWebViewControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TOWebViewControllerTests.m; sourceTree = "<group>"; };
@@ -382,6 +389,8 @@
 				221CE5EC18EEFC84002D89F5 /* Localizations */,
 				22F922591753B15E0034FEF6 /* TOWebViewController.h */,
 				22F9225A1753B15E0034FEF6 /* TOWebViewController.m */,
+				227D20A71CB52411008F641B /* TOWebHistoryitem.h */,
+				227D20A81CB52411008F641B /* TOWebHistoryitem.m */,
 				22AC285218E98FE1006DB0E9 /* TOActivitySafari.h */,
 				22AC285318E98FE1006DB0E9 /* TOActivitySafari.m */,
 				22AC285518E9920B006DB0E9 /* TOActivityChrome.h */,
@@ -453,6 +462,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				D2A653D71C7E872600566F54 /* NJKWebViewProgressView.h in Headers */,
+				227D20A91CB52411008F641B /* TOWebHistoryitem.h in Headers */,
 				D2A653DA1C7E873700566F54 /* TOActivitySafari.h in Headers */,
 				D2A653E51C7EAAF900566F54 /* TOWebViewController.h in Headers */,
 				D2A653DC1C7E873700566F54 /* TOActivityChrome.h in Headers */,
@@ -634,6 +644,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				2282AEEC1B2E473900BD92FC /* TOWebViewControllerTests.m in Sources */,
+				227D20AC1CB52411008F641B /* TOWebHistoryitem.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -652,6 +663,7 @@
 				2282E1D91C266D5600D53AB1 /* TOActivitySafari.m in Sources */,
 				2282E1DA1C266D5600D53AB1 /* main.m in Sources */,
 				2282E1DB1C266D5600D53AB1 /* TOWebViewController.m in Sources */,
+				227D20AB1CB52411008F641B /* TOWebHistoryitem.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -664,6 +676,7 @@
 				227C55FA1750F1F900FC3411 /* TOViewController.m in Sources */,
 				22CB13C518E8158000B948D8 /* UIImage+TOWebViewControllerIcons.m in Sources */,
 				227C55F91750F1F800FC3411 /* TOAppDelegate.m in Sources */,
+				227D20AA1CB52411008F641B /* TOWebHistoryitem.m in Sources */,
 				22AC285418E98FE1006DB0E9 /* TOActivitySafari.m in Sources */,
 				227C55FB1750F20700FC3411 /* main.m in Sources */,
 				2282E2001C27138600D53AB1 /* NJKWebViewProgressView.m in Sources */,
@@ -677,6 +690,7 @@
 			files = (
 				D2A653E11C7E873F00566F54 /* OnePasswordExtension.m in Sources */,
 				D2A653E41C7E874500566F54 /* TOWebViewController+1Password.m in Sources */,
+				227D20AD1CB52411008F641B /* TOWebHistoryitem.m in Sources */,
 				D2A653D91C7E872E00566F54 /* TOWebViewController.m in Sources */,
 				D2A653D51C7E871C00566F54 /* NJKWebViewProgress.m in Sources */,
 				D2A653D81C7E872B00566F54 /* NJKWebViewProgressView.m in Sources */,