網絡開發中,當公司已經使用 HTML5 技術實現同時適應 Android 和 iOS 等多個平臺的網頁時,這時每每須要咱們 iOS 平臺可以嵌入網頁並進行各類交互,那咱們應該怎麼作來實現這種需求呢?html
這裏咱們考慮的方案就是:使用 UIWebView 網頁控件git
然而考慮使用 UIWebView 進行混合編程的場景特色有:github
(1)排版複雜:一般包括圖片和文字的混排,還有可能包括連接須要支持點擊操做。若是本身用原生控件拼裝實現,因爲界面元素過多,作起來會很困難。就算是使用 CoreText 來實現,也須要本身實現至關多的複雜排版邏輯。web
(2)界面的變更需求頻繁:例如淘寶 App 的彩票頁面,可能經常須要更新界面以推出不一樣的活動。採用 UIWebView 嵌套頁面實現後,這類頁面不須要向 App Store 提交新的版本就能夠動態地更新,而原生控件實現的界面很難達到如此高的靈活性。編程
(3)界面對用戶的交互需求不復雜:由於 UIWebView 實現的交互效果與原生效果相比仍是會大打折扣,因此這類界面一般都沒有複雜的交互效果。這也是主流 App 大多采用混合 UIWebView 來實現界面而不是使用純 UIWebView 來實現界面的緣由之一,另外的緣由是純 UIWebView 實現的性能也沒原生的好,並且純 UIWebView 實現的話根本沒法經過 App Store 的審覈,他會建議您搞 Web App。瀏覽器
本隨筆簡單介紹如何使用 UIWebView 網頁控件:網絡
(1)簡單瀏覽器app
file://WhatsNewIniPhoneOS.pdf 讀取本地資源文件ide
http://www.apple.com 讀取網站內容工具
KenmuHuang 讀取百度搜索網站相關查詢內容
(2)頁面交互;能夠考慮使用模版引擎庫 GRMustache:https://github.com/groue/GRMustache,方便閱讀和更改渲染內容
(3)頁面調用 OC 方法;能夠考慮使用 JS 與 OC 交互庫「WebViewJavascriptBridge」:https://github.com/marcuswestin/WebViewJavascriptBridge,方便 JS 與 OC 之間發送消息進行交互
效果以下:
ViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface ViewController : UITableViewController 4 @property (copy, nonatomic) NSArray *arrSampleName; 5 6 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName; 7 8 @end
ViewController.m
1 #import "ViewController.h" 2 #import "SimpleBrowserViewController.h" 3 #import "PageInteractionViewController.h" 4 #import "PageCallOCFunctionViewController.h" 5 6 @interface ViewController () 7 - (void)layoutUI; 8 @end 9 10 @implementation ViewController 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 14 [self layoutUI]; 15 } 16 17 - (void)didReceiveMemoryWarning { 18 [super didReceiveMemoryWarning]; 19 // Dispose of any resources that can be recreated. 20 } 21 22 - (instancetype)initWithSampleNameArray:(NSArray *)arrSampleName { 23 if (self = [super initWithStyle:UITableViewStyleGrouped]) { 24 self.navigationItem.title = @"UIWebView 操做"; 25 self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil]; 26 27 _arrSampleName = arrSampleName; 28 } 29 return self; 30 } 31 32 - (void)layoutUI { 33 34 } 35 36 #pragma mark - UITableViewController相關方法重寫 37 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { 38 return 0.1; 39 } 40 41 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 42 return 1; 43 } 44 45 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 46 return [_arrSampleName count]; 47 } 48 49 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 50 static NSString *cellIdentifier = @"cell"; 51 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; 52 if (!cell) { 53 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]; 54 } 55 cell.textLabel.text = _arrSampleName[indexPath.row]; 56 return cell; 57 } 58 59 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 60 switch (indexPath.row) { 61 case 0: { 62 SimpleBrowserViewController *simpleBrowserVC = [SimpleBrowserViewController new]; 63 [self.navigationController pushViewController:simpleBrowserVC animated:YES]; 64 break; 65 } 66 case 1: { 67 PageInteractionViewController *pageInteractionVC = [PageInteractionViewController new]; 68 [self.navigationController pushViewController:pageInteractionVC animated:YES]; 69 break; 70 } 71 case 2: { 72 PageCallOCFunctionViewController *pageCallOCFunctionVC = [PageCallOCFunctionViewController new]; 73 [self.navigationController pushViewController:pageCallOCFunctionVC animated:YES]; 74 break; 75 } 76 default: 77 break; 78 } 79 } 80 81 @end
PrefixHeader.pch
1 #define kTitleOfSimpleBrowser @"簡單瀏覽器" 2 #define kTitleOfPageInteraction @"頁面交互" 3 #define kTitleOfPageCallOCFunction @"頁面調用 OC 方法" 4 5 #define kApplication [UIApplication sharedApplication]
SimpleBrowserViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface SimpleBrowserViewController : UIViewController <UISearchBarDelegate, UIWebViewDelegate> 4 @property (strong, nonatomic) UISearchBar *searchBar; 5 @property (strong, nonatomic) UIWebView *webView; 6 @property (strong, nonatomic) UIToolbar *toolbar; 7 @property (strong, nonatomic) UIBarButtonItem *barBtnBack; 8 @property (strong, nonatomic) UIBarButtonItem *barBtnForward; 9 10 @end
SimpleBrowserViewController.m
1 #import "SimpleBrowserViewController.h" 2 3 @interface SimpleBrowserViewController () 4 - (void)webViewBack; 5 - (void)webViewForward; 6 - (void)layoutUI; 7 - (void)sendRequest:(NSString *)requestURLStr; 8 - (void)changeBarButtonStatus; 9 @end 10 11 @implementation SimpleBrowserViewController 12 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 16 [self layoutUI]; 17 } 18 19 - (void)didReceiveMemoryWarning { 20 [super didReceiveMemoryWarning]; 21 // Dispose of any resources that can be recreated. 22 } 23 24 - (void)webViewBack { 25 [_webView goBack]; 26 } 27 28 - (void)webViewForward { 29 [_webView goForward]; 30 } 31 32 - (void)layoutUI { 33 self.navigationItem.title = kTitleOfSimpleBrowser; 34 self.automaticallyAdjustsScrollViewInsets = NO; //是否自動適應滾動視圖的內嵌入;默認爲YES,這裏設置爲NO,避免網頁控件中_UIWebViewScrollView的UIWebBrowserView位置偏移 35 36 CGRect rect = [[UIScreen mainScreen] bounds]; 37 static const CGFloat heightOfStatusBarAndNavigationBar = 64.0; 38 CGFloat widthOfScreen = rect.size.width; 39 CGFloat heightOfScreen = rect.size.height; 40 41 //添加搜索欄 42 _searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0, heightOfStatusBarAndNavigationBar, widthOfScreen, 44.0)]; 43 _searchBar.delegate = self; 44 _searchBar.placeholder = @"請輸入以file://或http開頭的地址"; 45 [self.view addSubview:_searchBar]; 46 47 //添加工具欄和左右按鈕(回退和前進) 48 _toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0, heightOfScreen - 44.0, 49 widthOfScreen, 44.0)]; 50 rect = CGRectMake(0.0, 0.0, 32.0, 32.0); 51 UIButton *btnBack = [UIButton buttonWithType:UIButtonTypeCustom]; 52 btnBack.frame = rect; 53 [btnBack setImage:[UIImage imageNamed:@"LastPageNormal"] forState:UIControlStateNormal]; 54 [btnBack setImage:[UIImage imageNamed:@"LastPageDisabled"] forState:UIControlStateDisabled]; 55 [btnBack addTarget:self 56 action:@selector(webViewBack) 57 forControlEvents:UIControlEventTouchUpInside]; 58 _barBtnBack = [[UIBarButtonItem alloc] initWithCustomView:btnBack]; 59 _barBtnBack.enabled = NO; 60 61 UIBarButtonItem *barBtnSpacing=[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil]; 62 63 UIButton *btnForward = [UIButton buttonWithType:UIButtonTypeCustom]; 64 btnForward.frame = rect; 65 [btnForward setImage:[UIImage imageNamed:@"NextPageNormal"] forState:UIControlStateNormal]; 66 [btnForward setImage:[UIImage imageNamed:@"NextPageDisabled"] forState:UIControlStateDisabled]; 67 [btnForward addTarget:self 68 action:@selector(webViewForward) 69 forControlEvents:UIControlEventTouchUpInside]; 70 _barBtnForward = [[UIBarButtonItem alloc] initWithCustomView:btnForward]; 71 _barBtnForward.enabled = NO; 72 73 _toolbar.items = @[ _barBtnBack, barBtnSpacing, _barBtnForward ]; 74 [self.view addSubview:_toolbar]; 75 76 //添加網頁控件 77 CGFloat heightOfWebView = heightOfScreen - _searchBar.frame.origin.y - _searchBar.frame.size.height - _toolbar.frame.size.height; 78 _webView = [[UIWebView alloc] initWithFrame:CGRectMake(0.0, heightOfStatusBarAndNavigationBar + _searchBar.frame.size.height, 79 widthOfScreen, heightOfWebView)]; 80 _webView.dataDetectorTypes = UIDataDetectorTypeAll; 81 _webView.delegate = self; 82 [self.view addSubview:_webView]; 83 } 84 85 - (void)sendRequest:(NSString *)requestURLStr { 86 if (requestURLStr.length > 0) { 87 NSURL *requestURL; 88 89 //加載bundle中的文件;網頁控件打開本地pdf、word文件依靠的並非他自身解析,而是依靠MIME Type識別文件類型並調用對應應用打開 90 if ([requestURLStr hasPrefix:@"file://"]) { 91 NSRange range = [requestURLStr rangeOfString:@"file://"]; 92 NSString *fileName = [requestURLStr substringFromIndex:range.length]; 93 requestURL = [[NSBundle mainBundle] URLForResource:fileName 94 withExtension:nil]; 95 } else { 96 //加載百度搜索網站的內容 97 if (![requestURLStr hasPrefix:@"http"]) { 98 requestURLStr = [NSString stringWithFormat:@"https://www.baidu.com/s?wd=%@", requestURLStr]; 99 } 100 //最終加載的仍是HTTP或者HTTPS協議的網站內容,進行編碼操做 101 requestURLStr = [requestURLStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; 102 requestURL = [NSURL URLWithString:requestURLStr]; 103 } 104 105 //加載請求地址內容 106 [_webView loadRequest:[NSURLRequest requestWithURL:requestURL]]; 107 } 108 } 109 110 - (void)changeBarButtonStatus { 111 _barBtnBack.enabled = _webView.canGoBack; 112 _barBtnForward.enabled = _webView.canGoForward; 113 } 114 115 #pragma mark - UIWebViewDelegate 116 - (void)webViewDidStartLoad:(UIWebView *)webView { 117 kApplication.networkActivityIndicatorVisible = YES; 118 } 119 120 - (void)webViewDidFinishLoad:(UIWebView *)webView { 121 kApplication.networkActivityIndicatorVisible = NO; 122 [self changeBarButtonStatus]; 123 } 124 125 - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 126 NSLog(@"Error: %@", error); 127 kApplication.networkActivityIndicatorVisible = NO; 128 UIAlertView *alertVCustom = [[UIAlertView alloc] initWithTitle:@"提示信息" 129 message:@"網絡鏈接錯誤" 130 delegate:nil 131 cancelButtonTitle:@"肯定" 132 otherButtonTitles:nil, nil]; 133 [alertVCustom show]; 134 } 135 136 #pragma mark - UISearchBarDelegate 137 - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { 138 [self sendRequest:searchBar.text]; 139 [_searchBar resignFirstResponder]; 140 } 141 142 @end
PageInteractionViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface PageInteractionViewController : UIViewController<UIWebViewDelegate> 4 @property (strong, nonatomic) UIWebView *webView; 5 6 @end
PageInteractionViewController.m
1 #import "PageInteractionViewController.h" 2 3 @interface PageInteractionViewController () 4 - (void)layoutUI; 5 - (void)sendRequest; 6 @end 7 8 @implementation PageInteractionViewController 9 10 - (void)viewDidLoad { 11 [super viewDidLoad]; 12 13 [self layoutUI]; 14 15 [self sendRequest]; 16 } 17 18 - (void)didReceiveMemoryWarning { 19 [super didReceiveMemoryWarning]; 20 // Dispose of any resources that can be recreated. 21 } 22 23 - (void)layoutUI { 24 self.navigationItem.title = kTitleOfPageInteraction; 25 self.automaticallyAdjustsScrollViewInsets = NO; //是否自動適應滾動視圖的內嵌入;默認爲YES,這裏設置爲NO,避免網頁控件中_UIWebViewScrollView的UIWebBrowserView位置偏移 26 27 CGRect rect = [[UIScreen mainScreen] bounds]; 28 static const CGFloat heightOfStatusBarAndNavigationBar = 64.0; 29 //添加網頁控件 30 _webView = [[UIWebView alloc] initWithFrame:CGRectMake(0.0, heightOfStatusBarAndNavigationBar, 31 rect.size.width, rect.size.height - heightOfStatusBarAndNavigationBar)]; 32 _webView.dataDetectorTypes = UIDataDetectorTypeAll; 33 _webView.delegate = self; 34 self.automaticallyAdjustsScrollViewInsets = NO; 35 [self.view addSubview:_webView]; 36 } 37 38 - (void)sendRequest { 39 NSString *htmlStr=@"<html>\ 40 <head><title>KenmuHuang's Blog</title></head>\ 41 <body style=\"color:#0044AA;\">\ 42 <h3 id=\"header\">I'm KenmuHuang</h3>\ 43 <p>More coding, more thinking. Stay hungry, stay foolish.</p>\ 44 <span>http://www.cnblogs.com/huangjianwu/</span>\ 45 </body>\ 46 </html>"; 47 [_webView loadHTMLString:htmlStr baseURL:nil]; 48 } 49 50 #pragma mark - UIWebViewDelegate 51 - (void)webViewDidStartLoad:(UIWebView *)webView { 52 kApplication.networkActivityIndicatorVisible = YES; 53 } 54 55 - (void)webViewDidFinishLoad:(UIWebView *)webView { 56 kApplication.networkActivityIndicatorVisible = NO; 57 NSLog(@"%@",[_webView stringByEvaluatingJavaScriptFromString:@"document.title"]); //打印內容爲html的title標籤內容:KenmuHuang's Blog 58 NSLog(@"%@",[_webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('header').innerHTML='KenmuHuang\\'s Blog';"]); //必須使用雙反斜杆來轉譯單引號 59 } 60 61 - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 62 NSLog(@"Error: %@", error); 63 kApplication.networkActivityIndicatorVisible = NO; 64 UIAlertView *alertVCustom = [[UIAlertView alloc] initWithTitle:@"提示信息" 65 message:@"網絡鏈接錯誤" 66 delegate:nil 67 cancelButtonTitle:@"肯定" 68 otherButtonTitles:nil, nil]; 69 [alertVCustom show]; 70 } 71 72 @end
RedirectURL.js
1 function showSheet(title, cancelButtonTitle, destructiveButtonTitle, otherButtonTitle) { 2 var url = 'KMActionSheet://?'; 3 var paramas = title + '&' + cancelButtonTitle + '&' + destructiveButtonTitle; 4 if(otherButtonTitle) { 5 paramas += '&' + otherButtonTitle; 6 } 7 window.location.href = url + encodeURIComponent(paramas); 8 } 9 10 var header = document.getElementById('header'); 11 if(header) { 12 header.onclick = function(){ 13 showSheet('系統提示', '取消', '肯定', null); 14 }; 15 }
PageCallOCFunctionViewController.h
1 #import <UIKit/UIKit.h> 2 3 @interface PageCallOCFunctionViewController : UIViewController<UIWebViewDelegate> 4 @property (strong, nonatomic) UIWebView *webView; 5 6 @end
PageCallOCFunctionViewController.m
1 #import "PageCallOCFunctionViewController.h" 2 3 @interface PageCallOCFunctionViewController () 4 - (void)layoutUI; 5 - (void)sendRequest; 6 - (void)showActionSheetWithTitle:(NSString *)title cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitle:(NSString *)otherButtonTitle; 7 @end 8 9 @implementation PageCallOCFunctionViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 14 [self layoutUI]; 15 16 [self sendRequest]; 17 } 18 19 - (void)didReceiveMemoryWarning { 20 [super didReceiveMemoryWarning]; 21 // Dispose of any resources that can be recreated. 22 } 23 24 - (void)layoutUI { 25 self.navigationItem.title = kTitleOfPageCallOCFunction; 26 self.automaticallyAdjustsScrollViewInsets = NO; //是否自動適應滾動視圖的內嵌入;默認爲YES,這裏設置爲NO,避免網頁控件中_UIWebViewScrollView的UIWebBrowserView位置偏移 27 28 CGRect rect = [[UIScreen mainScreen] bounds]; 29 static const CGFloat heightOfStatusBarAndNavigationBar = 64.0; 30 //添加網頁控件 31 _webView = [[UIWebView alloc] initWithFrame:CGRectMake(0.0, heightOfStatusBarAndNavigationBar, 32 rect.size.width, rect.size.height - heightOfStatusBarAndNavigationBar)]; 33 _webView.dataDetectorTypes = UIDataDetectorTypeAll; 34 _webView.delegate = self; 35 self.automaticallyAdjustsScrollViewInsets = NO; 36 [self.view addSubview:_webView]; 37 } 38 39 - (void)sendRequest { 40 NSString *htmlStr=@"<html>\ 41 <head><title>KenmuHuang's Blog</title></head>\ 42 <body style=\"color:#0044AA;\">\ 43 <h3 id=\"header\">I'm KenmuHuang, click me to show action sheet.</h3>\ 44 <p>More coding, more thinking. Stay hungry, stay foolish.</p>\ 45 <span>http://www.cnblogs.com/huangjianwu/</span>\ 46 </body>\ 47 </html>"; 48 [_webView loadHTMLString:htmlStr baseURL:nil]; 49 } 50 51 - (void)showActionSheetWithTitle:(NSString *)title cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitle:(NSString *)otherButtonTitle{ 52 UIActionSheet *actionSheet=[[UIActionSheet alloc] initWithTitle:title 53 delegate:nil 54 cancelButtonTitle:cancelButtonTitle 55 destructiveButtonTitle:destructiveButtonTitle 56 otherButtonTitles:otherButtonTitle, nil]; 57 [actionSheet showInView:self.view]; 58 } 59 60 #pragma mark - UIWebViewDelegate 61 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { 62 BOOL isStartLoad = YES; 63 if ([request.URL.scheme isEqualToString:@"kmactionsheet"]) { //request.URL.scheme 返回全小寫的內容 64 NSString *paramContent = request.URL.query; 65 NSArray *arrParam = [[paramContent stringByRemovingPercentEncoding] componentsSeparatedByString:@"&"]; 66 NSString *otherButtonTitle = nil; 67 if (arrParam.count > 3) { 68 otherButtonTitle = arrParam[3]; 69 } 70 71 [self showActionSheetWithTitle:arrParam[0] 72 cancelButtonTitle:arrParam[1] 73 destructiveButtonTitle:arrParam[2] 74 otherButtonTitle:otherButtonTitle]; 75 isStartLoad = NO; 76 } 77 return isStartLoad; 78 } 79 80 - (void)webViewDidStartLoad:(UIWebView *)webView { 81 kApplication.networkActivityIndicatorVisible = YES; 82 } 83 84 - (void)webViewDidFinishLoad:(UIWebView *)webView { 85 kApplication.networkActivityIndicatorVisible = NO; 86 87 //加載用於重定向地址的JavaScript內容 88 NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"RedirectURL.js" ofType:nil]; 89 NSString *jsContent = [NSString stringWithContentsOfFile:jsPath 90 encoding:NSUTF8StringEncoding 91 error:nil]; 92 [_webView stringByEvaluatingJavaScriptFromString:jsContent]; 93 } 94 95 - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error { 96 NSLog(@"Error: %@", error); 97 kApplication.networkActivityIndicatorVisible = NO; 98 UIAlertView *alertVCustom = [[UIAlertView alloc] initWithTitle:@"提示信息" 99 message:@"網絡鏈接錯誤" 100 delegate:nil 101 cancelButtonTitle:@"肯定" 102 otherButtonTitles:nil, nil]; 103 [alertVCustom show]; 104 } 105 106 @end
AppDelegate.h
1 #import <UIKit/UIKit.h> 2 3 @interface AppDelegate : UIResponder <UIApplicationDelegate> 4 5 @property (strong, nonatomic) UIWindow *window; 6 @property (strong, nonatomic) UINavigationController *navigationController; 7 8 @end
AppDelegate.m
1 #import "AppDelegate.h" 2 #import "ViewController.h" 3 4 @interface AppDelegate () 5 6 @end 7 8 @implementation AppDelegate 9 10 11 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 12 _window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 13 ViewController *viewController = [[ViewController alloc] 14 initWithSampleNameArray:@[ kTitleOfSimpleBrowser, 15 kTitleOfPageInteraction, 16 kTitleOfPageCallOCFunction ]]; 17 _navigationController = [[UINavigationController alloc] initWithRootViewController:viewController]; 18 _window.rootViewController = _navigationController; 19 //[_window addSubview:_navigationController.view]; //當_window.rootViewController關聯時,這一句無關緊要 20 [_window makeKeyAndVisible]; 21 return YES; 22 } 23 24 - (void)applicationWillResignActive:(UIApplication *)application { 25 } 26 27 - (void)applicationDidEnterBackground:(UIApplication *)application { 28 } 29 30 - (void)applicationWillEnterForeground:(UIApplication *)application { 31 } 32 33 - (void)applicationDidBecomeActive:(UIApplication *)application { 34 } 35 36 - (void)applicationWillTerminate:(UIApplication *)application { 37 } 38 39 @end