├── vue ├── static │ └── .gitkeep ├── .eslintignore ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── src │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── NativeTitle.vue │ │ ├── NativeRightBar.vue │ │ ├── Go.vue │ │ ├── ImageInput.vue │ │ ├── Hybrid.vue │ │ ├── HTTP.vue │ │ ├── Hello.vue │ │ └── ImageX.vue │ ├── store.js │ ├── main.js │ ├── App.vue │ ├── router │ │ └── index.js │ └── hybrid.js ├── .editorconfig ├── .postcssrc.js ├── build │ ├── dev-client.js │ ├── vue-loader.conf.js │ ├── build.js │ ├── webpack.dev.conf.js │ ├── check-versions.js │ ├── webpack.base.conf.js │ ├── utils.js │ ├── dev-server.js │ └── webpack.prod.conf.js ├── index.html ├── .babelrc ├── .eslintrc.js ├── README.md └── package.json ├── screenshot.png ├── react ├── src │ ├── logo.png │ ├── index.css │ ├── App.test.js │ ├── NativeTitle.js │ ├── Hybrid.js │ ├── NativeRightBar.js │ ├── App.css │ ├── App.js │ ├── Image.js │ ├── Hello.js │ ├── index.js │ ├── logo.svg │ └── registerServiceWorker.js ├── public │ ├── favicon.ico │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── ios ├── HybridDemo │ ├── vue.zip │ ├── react.zip │ ├── HybridDemo-Bridging-Header.h │ ├── UIViewController+BackButtonHandler.h │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── UIViewController+BackButtonHandler.m │ ├── HomeViewController.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── UIImagePickerController+Rx.swift │ ├── Info.plist │ ├── UIImagePickerController+RxCreate.swift │ ├── R.generated.swift │ ├── HybridViewController.swift │ └── Plugin.swift ├── HybridDemo.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── project.pbxproj ├── HybridDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── WorkspaceSettings.xcsettings ├── Podfile └── Podfile.lock ├── LICENSE ├── .gitignore └── README.md /vue/static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vue/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/screenshot.png -------------------------------------------------------------------------------- /vue/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /react/src/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/react/src/logo.png -------------------------------------------------------------------------------- /ios/HybridDemo/vue.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/ios/HybridDemo/vue.zip -------------------------------------------------------------------------------- /ios/HybridDemo/react.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/ios/HybridDemo/react.zip -------------------------------------------------------------------------------- /react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/react/public/favicon.ico -------------------------------------------------------------------------------- /react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dianqk/HybridDemo/HEAD/vue/src/assets/logo.png -------------------------------------------------------------------------------- /vue/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /vue/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /ios/HybridDemo/HybridDemo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "UIViewController+BackButtonHandler.h" 6 | -------------------------------------------------------------------------------- /vue/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ios/HybridDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | }); 9 | -------------------------------------------------------------------------------- /vue/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /ios/HybridDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/HybridDemo.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | 8 | 9 | -------------------------------------------------------------------------------- /vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | web 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /vue/src/components/NativeTitle.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 20 | -------------------------------------------------------------------------------- /react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /vue/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-runtime"], 12 | "env": { 13 | "test": { 14 | "presets": ["env", "stage-2"], 15 | "plugins": ["istanbul"] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /react/src/NativeTitle.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class NativeTitle extends Component { 5 | 6 | // shouldComponentUpdate(nextProps) { 7 | // return nextProps.title !== this.props.title 8 | // } 9 | 10 | render() { 11 | window.$native.title = this.props.title || '' 12 | return null 13 | } 14 | } 15 | 16 | NativeTitle.propTypes = { 17 | title: PropTypes.string 18 | }; 19 | -------------------------------------------------------------------------------- /vue/src/components/NativeRightBar.vue: -------------------------------------------------------------------------------- 1 | 3 | 4 | 23 | -------------------------------------------------------------------------------- /vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // required to lint *.vue files 13 | plugins: [ 14 | 'html' 15 | ], 16 | // add your custom rules here 17 | 'rules': { 18 | // allow debugger during development 19 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /vue/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | transformToRequire: { 13 | video: 'src', 14 | source: 'src', 15 | img: 'src', 16 | image: 'xlink:href' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | use_frameworks! 4 | inhibit_all_warnings! 5 | 6 | target 'HybridDemo' do 7 | 8 | pod 'RxSwift' 9 | pod 'RxCocoa' 10 | pod 'SnapKit' 11 | pod 'SwiftyJSON' 12 | pod 'R.swift' 13 | pod 'MBProgressHUD', '~> 1.0.0' 14 | 15 | end 16 | 17 | post_install do |installer| 18 | installer.pods_project.targets.each do |target| 19 | target.build_configurations.each do |config| 20 | config.build_settings['SWIFT_VERSION'] = '4.0' 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /vue/src/store.js: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex' 2 | import Vue from 'vue' 3 | import logo from './assets/logo.png' 4 | 5 | Vue.use(Vuex) 6 | 7 | const store = new Vuex.Store({ 8 | state: { 9 | count: 0, 10 | rightBarTitle: '', 11 | hybridPageTitle: 'Hybrid Page', 12 | selectedImage: logo, 13 | response: [] 14 | }, 15 | mutations: { 16 | increment (state) { 17 | state.count++ 18 | }, 19 | selectImage (state, image) { 20 | state.selectedImage = image 21 | } 22 | } 23 | }) 24 | 25 | export default store 26 | -------------------------------------------------------------------------------- /vue/README.md: -------------------------------------------------------------------------------- 1 | # web 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /vue/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import native from './hybrid' 7 | import store from './store' 8 | 9 | Vue.config.productionTip = false 10 | 11 | Vue.prototype.$native = native 12 | 13 | /* eslint-disable no-new */ 14 | new Vue({ 15 | el: '#app', 16 | router, 17 | store, 18 | template: '', 19 | components: { App } 20 | }) 21 | -------------------------------------------------------------------------------- /vue/src/components/Go.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | -------------------------------------------------------------------------------- /ios/HybridDemo/UIViewController+BackButtonHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+BackButtonHandler.h 3 | // HybridDemo 4 | // 5 | // Created by DianQK on 2017/5/24. 6 | // Copyright © 2017年 DianQK. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol BackButtonHandlerProtocol 12 | 13 | @optional 14 | 15 | // Override this method in UIViewController derived class to handle 'Back' button click 16 | 17 | -(BOOL)navigationShouldPopOnBackButton; 18 | 19 | @end 20 | 21 | @interface UIViewController (BackButtonHandler) 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-hybrid", 3 | "version": "0.1.0", 4 | "homepage": "https://canlis.github.io/canlis-react-example", 5 | "private": true, 6 | "dependencies": { 7 | "react": "^15.6.1", 8 | "react-dom": "^15.6.1", 9 | "react-router-dom": "^4.1.2", 10 | "react-scripts": "1.0.11" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject", 17 | "predeploy": "npm run build", 18 | "deploy": "gh-pages -d build" 19 | }, 20 | "devDependencies": { 21 | "gh-pages": "^1.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /react/src/Hybrid.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './App.css'; 3 | import NativeTitle from './NativeTitle' 4 | 5 | export default class extends Component { 6 | 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | title: 'Hybrid Page' 11 | } 12 | } 13 | 14 | handleChange = (event) => { 15 | this.setState({ title: event.target.value }) 16 | } 17 | 18 | render () { 19 | return ( 20 |
21 | 22 | Hybrid 23 | 24 |
25 | ); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /react/src/NativeRightBar.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class NativeRightBar extends Component { 5 | 6 | // shouldComponentUpdate(nextProps) { 7 | // return nextProps.title !== this.props.title 8 | // } 9 | 10 | componentWillMount () { 11 | window.$native.rightBarClick = () => { 12 | this.props.onClick() 13 | } 14 | } 15 | 16 | componentWillUnmount () { 17 | // window.$native.rightBarClick = null 18 | } 19 | 20 | render () { 21 | window.$native.rightBarTitle = this.props.title || '' 22 | return null 23 | } 24 | } 25 | 26 | NativeRightBar.propTypes = { 27 | title: PropTypes.string 28 | }; 29 | -------------------------------------------------------------------------------- /vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 50 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - MBProgressHUD (1.0.0) 3 | - R.swift (4.0.0): 4 | - R.swift.Library (~> 4.0.0) 5 | - R.swift.Library (4.0.0) 6 | - RxCocoa (4.0.0): 7 | - RxSwift (~> 4.0) 8 | - RxSwift (4.0.0) 9 | - SnapKit (4.0.0) 10 | - SwiftyJSON (3.1.4) 11 | 12 | DEPENDENCIES: 13 | - MBProgressHUD (~> 1.0.0) 14 | - R.swift 15 | - RxCocoa 16 | - RxSwift 17 | - SnapKit 18 | - SwiftyJSON 19 | 20 | SPEC CHECKSUMS: 21 | MBProgressHUD: 4890f671c94e8a0f3cf959aa731e9de2f036d71a 22 | R.swift: d6a5ec2f55a8441dc0ed9f1f8b37d7d11ae85c66 23 | R.swift.Library: c3af34921024333546e23b70e70d0b4e0cffca75 24 | RxCocoa: d62846ca96495d862fa4c59ea7d87e5031d7340e 25 | RxSwift: fd680d75283beb5e2559486f3c0ff852f0d35334 26 | SnapKit: a42d492c16e80209130a3379f73596c3454b7694 27 | SwiftyJSON: c2842d878f95482ffceec5709abc3d05680c0220 28 | 29 | PODFILE CHECKSUM: f9843cf97863b0c09b694bbc0fe6637f13525c66 30 | 31 | COCOAPODS: 1.3.1 32 | -------------------------------------------------------------------------------- /vue/src/components/ImageInput.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 33 | -------------------------------------------------------------------------------- /react/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 80px; 8 | } 9 | 10 | .App-header { 11 | background-color: #222; 12 | height: 150px; 13 | padding: 20px; 14 | color: white; 15 | } 16 | 17 | .App-intro { 18 | font-size: large; 19 | } 20 | 21 | @keyframes App-logo-spin { 22 | from { transform: rotate(0deg); } 23 | to { transform: rotate(360deg); } 24 | } 25 | 26 | body { 27 | -webkit-tap-highlight-color: rgba(0,0,0,0); 28 | min-height: 100vh; 29 | } 30 | 31 | #root { 32 | font-family: 'Avenir', Helvetica, Arial, sans-serif; 33 | -webkit-font-smoothing: antialiased; 34 | -moz-osx-font-smoothing: grayscale; 35 | text-align: center; 36 | color: #2c3e50; 37 | } 38 | 39 | h1, h2 { 40 | font-weight: normal; 41 | } 42 | 43 | ul { 44 | list-style-type: none; 45 | padding: 0; 46 | } 47 | 48 | li { 49 | display: inline-block; 50 | margin: 0 5px; 51 | } 52 | 53 | a { 54 | color: #42b983; 55 | } 56 | -------------------------------------------------------------------------------- /vue/src/components/Hybrid.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 32 | 33 | 53 | -------------------------------------------------------------------------------- /vue/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /ios/HybridDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HybridDemo 4 | // 5 | // Created by wc on 13/08/2017. 6 | // Copyright © 2017 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxCocoa 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | 19 | RxImagePickerDelegateProxy.register { RxImagePickerDelegateProxy(imagePicker: $0) } 20 | 21 | ScriptMessageService.plugins = [ 22 | TitlePlugin.self, 23 | SelectImagePlugin.self, 24 | RightBarTitlePlugin.self, 25 | LogPlugin.self, 26 | DisplayImagePlugin.self, 27 | HTTPRequestPlugin.self, 28 | LoadingPlugin.self, 29 | NavigationPlugin.self, 30 | NavigationGoPlugin.self, 31 | ToastPlugin.self 32 | ] 33 | 34 | return true 35 | } 36 | 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /vue/src/components/HTTP.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 DianQK 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /react/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | import { 5 | BrowserRouter as Router, 6 | Route, 7 | Link 8 | } from 'react-router-dom' 9 | import Hybrid from './Hybrid' 10 | import Hello from './Hello' 11 | 12 | class HybridRoute extends Route { 13 | 14 | render() { 15 | if (this.state.match) { 16 | // window.$native.title = this.props.title || '' 17 | window.$native.title = '' 18 | window.$native.rightBarTitle = '' 19 | } 20 | return super.render() 21 | } 22 | 23 | } 24 | 25 | class App extends Component { 26 | render() { 27 | return ( 28 |
29 |
30 | logo 31 |

Welcome to React

32 |
33 | 34 |
35 |
    36 |
  • Hybrid
  • 37 |
  • Hello
  • 38 |
39 | 40 | 41 | 42 |
43 |
44 |
45 | ); 46 | } 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /vue/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /react/src/Image.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | export default class Image extends Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | show: true 10 | } 11 | } 12 | 13 | // shouldComponentUpdate(nextProps) { 14 | // return nextProps.title !== this.props.title 15 | // } 16 | 17 | componentWillMount () { 18 | window.$native.rightBarClick = () => { 19 | this.props.onClick() 20 | } 21 | } 22 | 23 | componentWillUnmount () { 24 | // window.$native.rightBarClick = null 25 | } 26 | 27 | displayImage = async () => { 28 | if (!this.props.fullScreen) { 29 | return 30 | } 31 | let width = this.image.width 32 | let height = this.image.height 33 | let x = this.image.offsetLeft 34 | let y = this.image.offsetTop 35 | this.setState({ 36 | show: false 37 | }) 38 | await window.$native.event('displayImage', { x, y, width, height, image: this.props.src }) 39 | this.setState({ 40 | show: true 41 | }) 42 | } 43 | 44 | render () { 45 | return (this.state.show ? { this.image = img }} alt=""/> : null) 46 | } 47 | } 48 | 49 | Image.propTypes = { 50 | src: PropTypes.string, 51 | fullScreen: PropTypes.boolean 52 | }; 53 | -------------------------------------------------------------------------------- /ios/HybridDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /vue/src/components/Hello.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 52 | -------------------------------------------------------------------------------- /vue/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ios/HybridDemo/UIViewController+BackButtonHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+BackButtonHandler.m 3 | // HybridDemo 4 | // 5 | // Created by DianQK on 2017/5/24. 6 | // Copyright © 2017年 DianQK. All rights reserved. 7 | // 8 | 9 | #import "UIViewController+BackButtonHandler.h" 10 | 11 | @implementation UIViewController (BackButtonHandler) 12 | 13 | @end 14 | 15 | @implementation UINavigationController (ShouldPopOnBackButton) 16 | 17 | - (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item { 18 | 19 | if([self.viewControllers count] < [navigationBar.items count]) { 20 | return YES; 21 | } 22 | 23 | BOOL shouldPop = YES; 24 | UIViewController* vc = [self topViewController]; 25 | if([vc respondsToSelector:@selector(navigationShouldPopOnBackButton)]) { 26 | shouldPop = [vc navigationShouldPopOnBackButton]; 27 | } 28 | 29 | if(shouldPop) { 30 | dispatch_async(dispatch_get_main_queue(), ^{ 31 | [self popViewControllerAnimated:YES]; 32 | }); 33 | } else { 34 | // Workaround for iOS7.1. Thanks to @boliva - http://stackoverflow.com/posts/comments/34452906 35 | for(UIView *subview in [navigationBar subviews]) { 36 | if(0. < subview.alpha && subview.alpha < 1.) { 37 | [UIView animateWithDuration:.25 animations:^{ 38 | subview.alpha = 1.; 39 | }]; 40 | } 41 | } 42 | } 43 | 44 | return NO; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /react/src/Hello.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import './App.css' 3 | import NativeTitle from './NativeTitle' 4 | import NativeRightBar from './NativeRightBar' 5 | import Image from './Image' 6 | 7 | export default class extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | count: 0, 13 | selectedImage: 'https://facebook.github.io/react/img/logo.svg', 14 | rightBarTitle: 'Chat', 15 | } 16 | } 17 | 18 | handleClick = () => { 19 | this.setState((prevState) => ({ 20 | count: prevState.count + 1 21 | })) 22 | } 23 | 24 | selectImage = async () => { 25 | let response = await window.$native.event('selectImage') 26 | this.setState({ 27 | selectedImage: response.image 28 | }) 29 | } 30 | 31 | changeRightBarTitle = (title) => { 32 | this.setState({ 33 | rightBarTitle: title 34 | }) 35 | } 36 | 37 | render() { 38 | return ( 39 |
40 | 41 | 42 | 47 | {this.state.count} 48 |
49 | 50 |
51 |
52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vue/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: require('./prod.env'), 7 | index: path.resolve(__dirname, '../dist/index.html'), 8 | assetsRoot: path.resolve(__dirname, '../dist'), 9 | assetsSubDirectory: 'static', 10 | assetsPublicPath: './', 11 | productionSourceMap: true, 12 | // Gzip off by default as many popular static hosts such as 13 | // Surge or Netlify already gzip all static assets for you. 14 | // Before setting to `true`, make sure to: 15 | // npm install --save-dev compression-webpack-plugin 16 | productionGzip: false, 17 | productionGzipExtensions: ['js', 'css'], 18 | // Run the build command with an extra argument to 19 | // View the bundle analyzer report after build finishes: 20 | // `npm run build --report` 21 | // Set to `true` or `false` to always turn it on or off 22 | bundleAnalyzerReport: process.env.npm_config_report 23 | }, 24 | dev: { 25 | env: require('./dev.env'), 26 | port: 8080, 27 | autoOpenBrowser: true, 28 | assetsSubDirectory: 'static', 29 | assetsPublicPath: '/', 30 | proxyTable: {}, 31 | // CSS Sourcemaps off by default because relative paths are "buggy" 32 | // with this option, according to the CSS-Loader README 33 | // (https://github.com/webpack/css-loader#sourcemaps) 34 | // In our experience, they generally work as expected, 35 | // just be aware of this issue when enabling this option. 36 | cssSourceMap: false 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vue/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Hello from '@/components/Hello' 4 | import Hybrid from '@/components/Hybrid' 5 | import HTTP from '@/components/HTTP' 6 | import Go from '@/components/Go' 7 | 8 | Vue.use(Router) 9 | 10 | const router = new Router({ 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'Hello', 15 | component: Hello, 16 | meta: { title: 'Hello', rightBarTitle: 'Chat' } 17 | }, 18 | { 19 | path: '/hybrid', 20 | name: 'Hybrid', 21 | component: Hybrid, 22 | meta: { title: 'Hybrid Page'} 23 | }, 24 | { 25 | path: '/http', 26 | name: 'Http', 27 | component: HTTP, 28 | meta: { title: 'HTTP'} 29 | }, 30 | { 31 | path: '/go', 32 | name: 'Go', 33 | component: Go, 34 | meta: { title: 'Go'} 35 | } 36 | ] 37 | }) 38 | 39 | router.go = (n) => { 40 | window.$native.event('go', n) 41 | router.history.go(n) 42 | } 43 | 44 | router.back = () => { 45 | router.go(-1) 46 | } 47 | 48 | router.push = (location, onComplete, onAbort) => { 49 | let route = router.matcher.match(location) 50 | let title = route.meta && (route.meta.title || '') 51 | window.$native.event('navigation', { title }) 52 | router.history.push(location, onComplete, onAbort) 53 | } 54 | 55 | router.afterEach(route => { 56 | // window.$native.title = route.meta.title 57 | // window.$native.rightBarTitle = route.meta.rightBarTitle 58 | // window.$native.title = '' 59 | // window.$native.rightBarTitle = '' 60 | }) 61 | 62 | export default router 63 | -------------------------------------------------------------------------------- /ios/HybridDemo/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.swift 3 | // HybridDemo 4 | // 5 | // Created by wc on 15/08/2017. 6 | // Copyright © 2017 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import Rswift 13 | 14 | class HomeViewController: UIViewController { 15 | 16 | 17 | @IBOutlet weak var vueRemoteButton: UIButton! 18 | @IBOutlet weak var vueLocalhostButton: UIButton! 19 | @IBOutlet weak var reactRemoteButton: UIButton! 20 | @IBOutlet weak var reactLocalhostButton: UIButton! 21 | 22 | let disposeBag = DisposeBag() 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | Observable.merge([ 28 | vueRemoteButton.rx.tap.asObservable().map { URL(string: "https://canlis.github.io/canlis-vue-example/#/")! }, 29 | vueLocalhostButton.rx.tap.asObservable().map { URL(string: "http://localhost:8080/#/")! }, 30 | reactRemoteButton.rx.tap.asObservable().map { URL(string: "https://canlis.github.io/canlis-react-example/")! }, 31 | reactLocalhostButton.rx.tap.asObservable().map { URL(string: "http://localhost:3000/")! }, 32 | ]) 33 | .subscribe(onNext: { [unowned self] url in 34 | let hybridViewController = R.storyboard.main.hybridViewController()! 35 | _ = hybridViewController.view 36 | hybridViewController.webView.load(URLRequest(url: url)) 37 | self.navigationController?.pushViewController(hybridViewController, animated: true) 38 | }) 39 | .disposed(by: disposeBag) 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /vue/src/components/ImageX.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 44 | 45 | 71 | -------------------------------------------------------------------------------- /react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /ios/HybridDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ios/HybridDemo/UIImagePickerController+Rx.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImagePickerController+Rx.swift 3 | // RxExample 4 | // 5 | // Created by Segii Shulga on 1/4/16. 6 | // Copyright © 2016 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | 10 | #if os(iOS) 11 | 12 | #if !RX_NO_MODULE 13 | import RxSwift 14 | import RxCocoa 15 | #endif 16 | import UIKit 17 | 18 | extension Reactive where Base: UIImagePickerController { 19 | 20 | /** 21 | Reactive wrapper for `delegate` message. 22 | */ 23 | public var didFinishPickingMediaWithInfo: Observable<[String : AnyObject]> { 24 | return delegate 25 | .methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:))) 26 | .map({ (a) in 27 | return try castOrThrow(Dictionary.self, a[1]) 28 | }) 29 | } 30 | 31 | /** 32 | Reactive wrapper for `delegate` message. 33 | */ 34 | public var didCancel: Observable<()> { 35 | return delegate 36 | .methodInvoked(#selector(UIImagePickerControllerDelegate.imagePickerControllerDidCancel(_:))) 37 | .map {_ in () } 38 | } 39 | 40 | } 41 | 42 | #endif 43 | 44 | open class RxImagePickerDelegateProxy 45 | : RxNavigationControllerDelegateProxy, UIImagePickerControllerDelegate { 46 | 47 | public init(imagePicker: UIImagePickerController) { 48 | super.init(navigationController: imagePicker) 49 | } 50 | 51 | } 52 | 53 | fileprivate func castOrThrow(_ resultType: T.Type, _ object: Any) throws -> T { 54 | guard let returnValue = object as? T else { 55 | throw RxCocoaError.castingError(object: object, targetType: resultType) 56 | } 57 | 58 | return returnValue 59 | } 60 | -------------------------------------------------------------------------------- /react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | var native = {} 8 | 9 | window.$native = native 10 | 11 | var callbacks = {} 12 | 13 | native.callbacks = callbacks 14 | 15 | function generateUUID () { 16 | var d = new Date().getTime() 17 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 18 | var r = (d + Math.random()*16)%16 | 0 19 | d = Math.floor(d/16) 20 | return (c === 'x' ? r : ((r&0x7)|0x8)).toString(16) 21 | }) 22 | return uuid 23 | } 24 | 25 | Object.defineProperty(native, 'title', { set: (title) => { 26 | title = title || '' 27 | if (window.webkit) { 28 | window.webkit.messageHandlers.title.postMessage({ title }) 29 | } 30 | } }) 31 | 32 | Object.defineProperty(native, 'rightBarTitle', { set: (title) => { 33 | title = title || '' 34 | if (window.webkit) { 35 | window.webkit.messageHandlers.rightBarTitle.postMessage({ title }) 36 | } 37 | } }) 38 | 39 | let webLog = console.log 40 | 41 | console.log = (...message) => { 42 | if (window.webkit) { 43 | window.webkit.messageHandlers.log.postMessage(`${JSON.stringify(message)}`) 44 | } 45 | webLog(message) 46 | } 47 | 48 | native.event = (name, params) => { 49 | let uuid = generateUUID() 50 | callbacks[uuid] = {} 51 | let message = { 52 | callbackId: uuid, 53 | content: params || {} 54 | } 55 | let promise = new Promise(function(resolve, reject) { 56 | callbacks[uuid].callback = (res) => { 57 | resolve(res) 58 | delete callbacks[uuid] 59 | } 60 | }) 61 | window.webkit.messageHandlers[name].postMessage(message) 62 | return promise 63 | } 64 | 65 | ReactDOM.render(, document.getElementById('root')); 66 | registerServiceWorker(); 67 | -------------------------------------------------------------------------------- /ios/HybridDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | NSCameraUsageDescription 29 | We need camera 30 | NSLocationAlwaysUsageDescription 31 | We need location 32 | NSPhotoLibraryUsageDescription 33 | We need photo library 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UISupportedInterfaceOrientations 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # misc 2 | .DS_Store 3 | .env.local 4 | .env.development.local 5 | .env.test.local 6 | .env.production.local 7 | 8 | node_modules/ 9 | dist/ 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | 21 | # Xcode 22 | # 23 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 24 | 25 | ## Build generated 26 | DerivedData/ 27 | 28 | ## Various settings 29 | *.pbxuser 30 | !default.pbxuser 31 | *.mode1v3 32 | !default.mode1v3 33 | *.mode2v3 34 | !default.mode2v3 35 | *.perspectivev3 36 | !default.perspectivev3 37 | xcuserdata/ 38 | 39 | ## Other 40 | *.moved-aside 41 | *.xccheckout 42 | *.xcscmblueprint 43 | 44 | ## Obj-C/Swift specific 45 | *.hmap 46 | *.ipa 47 | *.dSYM.zip 48 | *.dSYM 49 | 50 | ## Playgrounds 51 | timeline.xctimeline 52 | playground.xcworkspace 53 | 54 | # Swift Package Manager 55 | # 56 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 57 | # Packages/ 58 | # Package.pins 59 | .build/ 60 | 61 | # CocoaPods 62 | # 63 | # We recommend against adding the Pods directory to your .gitignore. However 64 | # you should judge for yourself, the pros and cons are mentioned at: 65 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 66 | # 67 | Pods/ 68 | 69 | # Carthage 70 | # 71 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 72 | # Carthage/Checkouts 73 | 74 | Carthage/Build 75 | 76 | # fastlane 77 | # 78 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 79 | # screenshots whenever they are needed. 80 | # For more information about the recommended setup visit: 81 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 82 | 83 | fastlane/report.xml 84 | fastlane/Preview.html 85 | fastlane/screenshots 86 | fastlane/test_output 87 | -------------------------------------------------------------------------------- /vue/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 63 | } 64 | }, 65 | { 66 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 67 | loader: 'url-loader', 68 | options: { 69 | limit: 10000, 70 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 71 | } 72 | } 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "bigbigchai ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "lint": "eslint --ext .js,.vue src" 12 | }, 13 | "dependencies": { 14 | "vue": "^2.3.3", 15 | "vue-router": "^2.6.0", 16 | "vuex": "^2.4.0" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-eslint": "^7.1.1", 22 | "babel-loader": "^7.1.1", 23 | "babel-plugin-transform-runtime": "^6.22.0", 24 | "babel-preset-env": "^1.3.2", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "babel-register": "^6.22.0", 27 | "chalk": "^2.0.1", 28 | "connect-history-api-fallback": "^1.3.0", 29 | "copy-webpack-plugin": "^4.0.1", 30 | "css-loader": "^0.28.0", 31 | "cssnano": "^3.10.0", 32 | "eslint": "^3.19.0", 33 | "eslint-friendly-formatter": "^3.0.0", 34 | "eslint-loader": "^1.7.1", 35 | "eslint-plugin-html": "^3.0.0", 36 | "eventsource-polyfill": "^0.9.6", 37 | "express": "^4.14.1", 38 | "extract-text-webpack-plugin": "^2.0.0", 39 | "file-loader": "^0.11.1", 40 | "friendly-errors-webpack-plugin": "^1.1.3", 41 | "html-webpack-plugin": "^2.28.0", 42 | "http-proxy-middleware": "^0.17.3", 43 | "less": "^2.7.2", 44 | "less-loader": "^4.0.5", 45 | "opn": "^5.1.0", 46 | "optimize-css-assets-webpack-plugin": "^2.0.0", 47 | "ora": "^1.2.0", 48 | "rimraf": "^2.6.0", 49 | "semver": "^5.3.0", 50 | "shelljs": "^0.7.6", 51 | "url-loader": "^0.5.8", 52 | "vue-loader": "^12.1.0", 53 | "vue-style-loader": "^3.0.1", 54 | "vue-template-compiler": "^2.3.3", 55 | "webpack": "^2.6.1", 56 | "webpack-bundle-analyzer": "^2.2.1", 57 | "webpack-dev-middleware": "^1.10.0", 58 | "webpack-hot-middleware": "^2.18.0", 59 | "webpack-merge": "^4.1.0" 60 | }, 61 | "engines": { 62 | "node": ">= 4.0.0", 63 | "npm": ">= 3.0.0" 64 | }, 65 | "browserslist": [ 66 | "> 1%", 67 | "last 2 versions", 68 | "not ie <= 8" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /vue/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /ios/HybridDemo/UIImagePickerController+RxCreate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImagePickerController+RxCreate.swift 3 | // RxExample 4 | // 5 | // Created by Krunoslav Zaher on 1/10/16. 6 | // Copyright © 2016 Krunoslav Zaher. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | #if !RX_NO_MODULE 12 | import RxSwift 13 | import RxCocoa 14 | #endif 15 | 16 | func dismissViewController(_ viewController: UIViewController, animated: Bool) { 17 | if viewController.isBeingDismissed || viewController.isBeingPresented { 18 | DispatchQueue.main.async { 19 | dismissViewController(viewController, animated: animated) 20 | } 21 | 22 | return 23 | } 24 | 25 | if viewController.presentingViewController != nil { 26 | viewController.dismiss(animated: animated, completion: nil) 27 | } 28 | } 29 | 30 | extension Reactive where Base: UIImagePickerController { 31 | static func createWithParent(_ parent: UIViewController?, animated: Bool = true, configureImagePicker: @escaping (UIImagePickerController) throws -> () = { x in }) -> Observable { 32 | return Observable.create { [weak parent] observer in 33 | let imagePicker = UIImagePickerController() 34 | let dismissDisposable = imagePicker.rx 35 | .didCancel 36 | .subscribe(onNext: { [weak imagePicker] in 37 | guard let imagePicker = imagePicker else { 38 | return 39 | } 40 | dismissViewController(imagePicker, animated: animated) 41 | }) 42 | 43 | do { 44 | try configureImagePicker(imagePicker) 45 | } 46 | catch let error { 47 | observer.on(.error(error)) 48 | return Disposables.create() 49 | } 50 | 51 | guard let parent = parent else { 52 | observer.on(.completed) 53 | return Disposables.create() 54 | } 55 | 56 | parent.present(imagePicker, animated: animated, completion: nil) 57 | observer.on(.next(imagePicker)) 58 | 59 | return Disposables.create(dismissDisposable, Disposables.create { 60 | dismissViewController(imagePicker, animated: animated) 61 | }) 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vue/src/hybrid.js: -------------------------------------------------------------------------------- 1 | class NativeError { // extends Error { 2 | constructor ({ code, message }) { 3 | // super() 4 | this.code = code 5 | this.message = message 6 | } 7 | } 8 | 9 | class Native { 10 | 11 | get embedded () { 12 | return window.webkit != undefined // You can change this, write your embedded 13 | } 14 | 15 | set title (title) { 16 | title = title || '' 17 | this.perform('title', { title }) 18 | } 19 | 20 | set rightBarTitle (title) { 21 | title = title || '' 22 | this.perform('rightBarTitle', { title }) 23 | } 24 | 25 | _loadingCount = 0 26 | set $loading (value) { 27 | if (value) { 28 | this._loadingCount = this._loadingCount + 1 29 | } else { 30 | if (this._loadingCount <= 0) { 31 | return 32 | } 33 | this._loadingCount = this._loadingCount - 1 34 | } 35 | if (this._loadingCount === 0) { 36 | this.event('loading', false) 37 | } 38 | if (this._loadingCount === 1) { 39 | this.event('loading', true) 40 | } 41 | } 42 | get $loading () { 43 | return this._loadingCount > 0 44 | } 45 | 46 | callbacks = {} 47 | 48 | event (name, params) { 49 | let uuid = generateUUID() 50 | let message = { 51 | callbackId: uuid, 52 | content: params || {} 53 | } 54 | let promise = new Promise((resolve, reject) => { 55 | this.callbacks[uuid] = { 56 | callback: (res) => { 57 | if (res.error) { 58 | reject(new NativeError(res.error)) 59 | } else { 60 | resolve(res) 61 | } 62 | delete this.callbacks[uuid] 63 | } 64 | } 65 | }) 66 | this.perform(name, message) 67 | return promise 68 | } 69 | 70 | perform (name, message) { 71 | if (!this.embedded) { 72 | return 73 | } 74 | if ((window.webkit) && window.webkit.messageHandlers[name]) { 75 | window.webkit.messageHandlers[name].postMessage(message) 76 | } 77 | } 78 | 79 | constructor() { 80 | window.$native = this 81 | } 82 | 83 | } 84 | 85 | const native = new Native() 86 | 87 | function generateUUID () { 88 | var d = new Date().getTime() 89 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 90 | var r = (d + Math.random()*16)%16 | 0 91 | d = Math.floor(d/16) 92 | return (c=='x' ? r : (r&0x7|0x8)).toString(16) 93 | }) 94 | return uuid 95 | } 96 | 97 | export default native 98 | -------------------------------------------------------------------------------- /react/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /vue/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }) 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: false, 33 | heartbeat: 2000 34 | }) 35 | // force page reload when html-webpack-plugin template changes 36 | compiler.plugin('compilation', function (compilation) { 37 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 38 | hotMiddleware.publish({ action: 'reload' }) 39 | cb() 40 | }) 41 | }) 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(function (context) { 45 | var options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(options.filter || context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | // serve webpack bundle output 56 | app.use(devMiddleware) 57 | 58 | // enable hot-reload and state-preserving 59 | // compilation error display 60 | app.use(hotMiddleware) 61 | 62 | // serve pure static assets 63 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 64 | app.use(staticPath, express.static('./static')) 65 | 66 | var uri = 'http://localhost:' + port 67 | 68 | var _resolve 69 | var readyPromise = new Promise(resolve => { 70 | _resolve = resolve 71 | }) 72 | 73 | console.log('> Starting dev server...') 74 | devMiddleware.waitUntilValid(() => { 75 | console.log('> Listening at ' + uri + '\n') 76 | // when env is testing, don't need open it 77 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 78 | opn(uri) 79 | } 80 | _resolve() 81 | }) 82 | 83 | var server = app.listen(port) 84 | 85 | module.exports = { 86 | ready: readyPromise, 87 | close: () => { 88 | server.close() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /vue/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = config.build.env 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env 31 | }), 32 | new webpack.optimize.UglifyJsPlugin({ 33 | compress: { 34 | warnings: false 35 | }, 36 | sourceMap: true 37 | }), 38 | // extract css into its own file 39 | new ExtractTextPlugin({ 40 | filename: utils.assetsPath('css/[name].[contenthash].css') 41 | }), 42 | // Compress extracted CSS. We are using this plugin so that possible 43 | // duplicated CSS from different components can be deduped. 44 | new OptimizeCSSPlugin({ 45 | cssProcessorOptions: { 46 | safe: true 47 | } 48 | }), 49 | // generate dist index.html with correct asset hash for caching. 50 | // you can customize output by editing /index.html 51 | // see https://github.com/ampedandwired/html-webpack-plugin 52 | new HtmlWebpackPlugin({ 53 | filename: config.build.index, 54 | template: 'index.html', 55 | inject: true, 56 | minify: { 57 | removeComments: true, 58 | collapseWhitespace: true, 59 | removeAttributeQuotes: true 60 | // more options: 61 | // https://github.com/kangax/html-minifier#options-quick-reference 62 | }, 63 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 64 | chunksSortMode: 'dependency' 65 | }), 66 | // split vendor js into its own file 67 | new webpack.optimize.CommonsChunkPlugin({ 68 | name: 'vendor', 69 | minChunks: function (module, count) { 70 | // any required modules inside node_modules are extracted to vendor 71 | return ( 72 | module.resource && 73 | /\.js$/.test(module.resource) && 74 | module.resource.indexOf( 75 | path.join(__dirname, '../node_modules') 76 | ) === 0 77 | ) 78 | } 79 | }), 80 | // extract webpack runtime and module manifest to its own file in order to 81 | // prevent vendor hash from being updated whenever app bundle is updated 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'manifest', 84 | chunks: ['vendor'] 85 | }), 86 | // copy custom static assets 87 | new CopyWebpackPlugin([ 88 | { 89 | from: path.resolve(__dirname, '../static'), 90 | to: config.build.assetsSubDirectory, 91 | ignore: ['.*'] 92 | } 93 | ]) 94 | ] 95 | }) 96 | 97 | if (config.build.productionGzip) { 98 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 99 | 100 | webpackConfig.plugins.push( 101 | new CompressionWebpackPlugin({ 102 | asset: '[path].gz[query]', 103 | algorithm: 'gzip', 104 | test: new RegExp( 105 | '\\.(' + 106 | config.build.productionGzipExtensions.join('|') + 107 | ')$' 108 | ), 109 | threshold: 10240, 110 | minRatio: 0.8 111 | }) 112 | ) 113 | } 114 | 115 | if (config.build.bundleAnalyzerReport) { 116 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 117 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 118 | } 119 | 120 | module.exports = webpackConfig 121 | -------------------------------------------------------------------------------- /react/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (!isLocalhost) { 36 | // Is not local host. Just register service worker 37 | registerValidSW(swUrl); 38 | } else { 39 | // This is running on localhost. Lets check if a service worker still exists or not. 40 | checkValidServiceWorker(swUrl); 41 | } 42 | }); 43 | } 44 | } 45 | 46 | function registerValidSW(swUrl) { 47 | navigator.serviceWorker 48 | .register(swUrl) 49 | .then(registration => { 50 | registration.onupdatefound = () => { 51 | const installingWorker = registration.installing; 52 | installingWorker.onstatechange = () => { 53 | if (installingWorker.state === 'installed') { 54 | if (navigator.serviceWorker.controller) { 55 | // At this point, the old content will have been purged and 56 | // the fresh content will have been added to the cache. 57 | // It's the perfect time to display a "New content is 58 | // available; please refresh." message in your web app. 59 | console.log('New content is available; please refresh.'); 60 | } else { 61 | // At this point, everything has been precached. 62 | // It's the perfect time to display a 63 | // "Content is cached for offline use." message. 64 | console.log('Content is cached for offline use.'); 65 | } 66 | } 67 | }; 68 | }; 69 | }) 70 | .catch(error => { 71 | console.error('Error during service worker registration:', error); 72 | }); 73 | } 74 | 75 | function checkValidServiceWorker(swUrl) { 76 | // Check if the service worker can be found. If it can't reload the page. 77 | fetch(swUrl) 78 | .then(response => { 79 | // Ensure service worker exists, and that we really are getting a JS file. 80 | if ( 81 | response.status === 404 || 82 | response.headers.get('content-type').indexOf('javascript') === -1 83 | ) { 84 | // No service worker found. Probably a different app. Reload the page. 85 | navigator.serviceWorker.ready.then(registration => { 86 | registration.unregister().then(() => { 87 | window.location.reload(); 88 | }); 89 | }); 90 | } else { 91 | // Service worker found. Proceed as normal. 92 | registerValidSW(swUrl); 93 | } 94 | }) 95 | .catch(() => { 96 | console.log( 97 | 'No internet connection found. App is running in offline mode.' 98 | ); 99 | }); 100 | } 101 | 102 | export function unregister() { 103 | if ('serviceWorker' in navigator) { 104 | navigator.serviceWorker.ready.then(registration => { 105 | registration.unregister(); 106 | }); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ios/HybridDemo/R.generated.swift: -------------------------------------------------------------------------------- 1 | // 2 | // This is a generated file, do not edit! 3 | // Generated by R.swift, see https://github.com/mac-cain13/R.swift 4 | // 5 | 6 | import Foundation 7 | import Rswift 8 | import UIKit 9 | 10 | /// This `R` struct is generated and contains references to static resources. 11 | struct R: Rswift.Validatable { 12 | fileprivate static let applicationLocale = hostingBundle.preferredLocalizations.first.flatMap(Locale.init) ?? Locale.current 13 | fileprivate static let hostingBundle = Bundle(for: R.Class.self) 14 | 15 | static func validate() throws { 16 | try intern.validate() 17 | } 18 | 19 | /// This `R.color` struct is generated, and contains static references to 0 colors. 20 | struct color { 21 | fileprivate init() {} 22 | } 23 | 24 | /// This `R.file` struct is generated, and contains static references to 2 files. 25 | struct file { 26 | /// Resource file `react.zip`. 27 | static let reactZip = Rswift.FileResource(bundle: R.hostingBundle, name: "react", pathExtension: "zip") 28 | /// Resource file `vue.zip`. 29 | static let vueZip = Rswift.FileResource(bundle: R.hostingBundle, name: "vue", pathExtension: "zip") 30 | 31 | /// `bundle.url(forResource: "react", withExtension: "zip")` 32 | static func reactZip(_: Void = ()) -> Foundation.URL? { 33 | let fileResource = R.file.reactZip 34 | return fileResource.bundle.url(forResource: fileResource) 35 | } 36 | 37 | /// `bundle.url(forResource: "vue", withExtension: "zip")` 38 | static func vueZip(_: Void = ()) -> Foundation.URL? { 39 | let fileResource = R.file.vueZip 40 | return fileResource.bundle.url(forResource: fileResource) 41 | } 42 | 43 | fileprivate init() {} 44 | } 45 | 46 | /// This `R.font` struct is generated, and contains static references to 0 fonts. 47 | struct font { 48 | fileprivate init() {} 49 | } 50 | 51 | /// This `R.image` struct is generated, and contains static references to 0 images. 52 | struct image { 53 | fileprivate init() {} 54 | } 55 | 56 | /// This `R.nib` struct is generated, and contains static references to 0 nibs. 57 | struct nib { 58 | fileprivate init() {} 59 | } 60 | 61 | /// This `R.reuseIdentifier` struct is generated, and contains static references to 0 reuse identifiers. 62 | struct reuseIdentifier { 63 | fileprivate init() {} 64 | } 65 | 66 | /// This `R.segue` struct is generated, and contains static references to 0 view controllers. 67 | struct segue { 68 | fileprivate init() {} 69 | } 70 | 71 | /// This `R.storyboard` struct is generated, and contains static references to 2 storyboards. 72 | struct storyboard { 73 | /// Storyboard `LaunchScreen`. 74 | static let launchScreen = _R.storyboard.launchScreen() 75 | /// Storyboard `Main`. 76 | static let main = _R.storyboard.main() 77 | 78 | /// `UIStoryboard(name: "LaunchScreen", bundle: ...)` 79 | static func launchScreen(_: Void = ()) -> UIKit.UIStoryboard { 80 | return UIKit.UIStoryboard(resource: R.storyboard.launchScreen) 81 | } 82 | 83 | /// `UIStoryboard(name: "Main", bundle: ...)` 84 | static func main(_: Void = ()) -> UIKit.UIStoryboard { 85 | return UIKit.UIStoryboard(resource: R.storyboard.main) 86 | } 87 | 88 | fileprivate init() {} 89 | } 90 | 91 | /// This `R.string` struct is generated, and contains static references to 0 localization tables. 92 | struct string { 93 | fileprivate init() {} 94 | } 95 | 96 | fileprivate struct intern: Rswift.Validatable { 97 | fileprivate static func validate() throws { 98 | try _R.validate() 99 | } 100 | 101 | fileprivate init() {} 102 | } 103 | 104 | fileprivate class Class {} 105 | 106 | fileprivate init() {} 107 | } 108 | 109 | struct _R: Rswift.Validatable { 110 | static func validate() throws { 111 | try storyboard.validate() 112 | } 113 | 114 | struct nib { 115 | fileprivate init() {} 116 | } 117 | 118 | struct storyboard: Rswift.Validatable { 119 | static func validate() throws { 120 | try main.validate() 121 | } 122 | 123 | struct launchScreen: Rswift.StoryboardResourceWithInitialControllerType { 124 | typealias InitialController = UIKit.UIViewController 125 | 126 | let bundle = R.hostingBundle 127 | let name = "LaunchScreen" 128 | 129 | fileprivate init() {} 130 | } 131 | 132 | struct main: Rswift.StoryboardResourceWithInitialControllerType, Rswift.Validatable { 133 | typealias InitialController = UIKit.UINavigationController 134 | 135 | let bundle = R.hostingBundle 136 | let hybridViewController = StoryboardViewControllerResource(identifier: "HybridViewController") 137 | let name = "Main" 138 | 139 | func hybridViewController(_: Void = ()) -> HybridViewController? { 140 | return UIKit.UIStoryboard(resource: self).instantiateViewController(withResource: hybridViewController) 141 | } 142 | 143 | static func validate() throws { 144 | if _R.storyboard.main().hybridViewController() == nil { throw Rswift.ValidationError(description:"[R.swift] ViewController with identifier 'hybridViewController' could not be loaded from storyboard 'Main' as 'HybridViewController'.") } 145 | } 146 | 147 | fileprivate init() {} 148 | } 149 | 150 | fileprivate init() {} 151 | } 152 | 153 | fileprivate init() {} 154 | } 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HybridDemo 2 | A hybrid project for iOS, Vue and React. 3 | 4 | [![](https://raw.githubusercontent.com/DianQK/HybridDemo/master/screenshot.png)](https://vimeo.com/229822870) 5 | 6 | https://vimeo.com/229822870 7 | 8 | 9 | ### 选择图片 10 | 11 | #### iOS 12 | 13 | ```swift 14 | struct SelectImagePlugin: CallBackHybridPlugin { 15 | 16 | static var name: String { 17 | return "selectImage" 18 | } 19 | 20 | static func didReceive(message: JSON, webView: WKWebView, viewController: UIViewController) -> Observable { 21 | return Observable 22 | .create { (observer) -> Disposable in 23 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 24 | alert.addAction(UIAlertAction(title: "拍照", style: .default, handler: { _ in 25 | observer.onNext(UIImagePickerControllerSourceType.camera) 26 | observer.onCompleted() 27 | })) 28 | alert.addAction(UIAlertAction(title: "从相册选择", style: .default, handler: { _ in 29 | observer.onNext(UIImagePickerControllerSourceType.photoLibrary) 30 | observer.onCompleted() 31 | })) 32 | alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil)) 33 | viewController.present(alert, animated: true, completion: nil) 34 | return Disposables.create { 35 | alert.dismiss(animated: true, completion: nil) 36 | } 37 | } 38 | .flatMap { sourceType in 39 | UIImagePickerController.rx.createWithParent(viewController) { picker in 40 | picker.sourceType = sourceType 41 | picker.allowsEditing = true 42 | } 43 | } 44 | .flatMap { $0.rx.didFinishPickingMediaWithInfo } 45 | .take(1) 46 | .map { return $0[UIImagePickerControllerEditedImage] as! UIImage } 47 | .map { UIImagePNGRepresentation($0)!.base64EncodedString() } 48 | .map { return JSON(["image": "data:img/jpg;base64," + $0]) } 49 | } 50 | 51 | } 52 | ``` 53 | 54 | #### JavaScript 55 | 56 | ```js 57 | let response = window.$native.event('selectImage') 58 | this.selectedImage = response.image 59 | ``` 60 | 61 | ### 修改标题 62 | 63 | #### iOS 64 | 65 | ```swift 66 | struct TitlePlugin: HybridPlugin { 67 | 68 | static var name: String { 69 | return "title" 70 | } 71 | 72 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView, viewController: UIViewController)>) -> Disposable { 73 | return message 74 | .subscribe(onNext: { (message, webView, viewController) in 75 | let title = message["title"].string 76 | viewController.title = title 77 | }) 78 | } 79 | 80 | } 81 | ``` 82 | 83 | #### Vue & React 84 | 85 | ```jsx 86 | 87 | ``` 88 | 89 | ### 图片放大 90 | 91 | #### iOS 92 | 93 | ```swift 94 | struct DisplayImagePlugin: CallBackHybridPlugin { 95 | 96 | static var name: String { 97 | return "displayImage" 98 | } 99 | 100 | static func didReceive(message: JSON, webView: WKWebView, viewController: UIViewController) -> Observable { 101 | guard let image = URL(string: message["image"].stringValue).flatMap({ try? Data(contentsOf: $0) }).flatMap({ UIImage(data: $0) }) else { 102 | return Observable.just(JSON([:])) 103 | } 104 | let frame = CGRect( 105 | x: message["x"].doubleValue, 106 | y: message["y"].doubleValue + Double(webView.frame.origin.y) - Double(webView.scrollView.contentOffset.y), 107 | width: message["width"].doubleValue, 108 | height: message["height"].doubleValue 109 | ) 110 | let keyWindow = UIApplication.shared.keyWindow! 111 | let displayView = DisplayView(frame: keyWindow.bounds) 112 | displayView.display(image: image, frame: frame) 113 | return displayView.displayFinished.ifEmpty(default: ()).map { JSON([:]) } 114 | } 115 | } 116 | ``` 117 | 118 | #### Vue 119 | 120 | ```Vue 121 | 122 | ``` 123 | 124 | #### React 125 | 126 | ```jsx 127 | 128 | ``` 129 | 130 | ### 右上角按钮点击 131 | 132 | #### iOS 133 | 134 | ```swift 135 | struct RightBarTitlePlugin: HybridPlugin { 136 | 137 | static var name: String { 138 | return "rightBarTitle" 139 | } 140 | 141 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView, viewController: UIViewController)>) -> Disposable { 142 | return message 143 | .flatMapLatest { (message, webView, viewController) -> Observable in 144 | let title = message["title"].stringValue 145 | if title.isEmpty { 146 | viewController.navigationItem.rightBarButtonItem = nil 147 | return Observable.empty() 148 | } 149 | let rightBarButtonItem = UIBarButtonItem(title: title, style: UIBarButtonItemStyle.plain, target: nil, action: nil) 150 | viewController.navigationItem.rightBarButtonItem = rightBarButtonItem 151 | return rightBarButtonItem.rx.tap 152 | .map { webView } 153 | } 154 | .subscribe(onNext: { (webView) in 155 | webView.evaluateJavaScript("window.$native.rightBarClick();", completionHandler: nil) 156 | }) 157 | } 158 | 159 | } 160 | ``` 161 | 162 | #### Vue 163 | 164 | ```Vue 165 | 166 | ``` 167 | 168 | #### React 169 | 170 | ```jsx 171 | 172 | ``` 173 | 174 | ## TODO 175 | 176 | - [ ] Support Web 177 | - [ ] Input Image Component 178 | - [ ] Support Android 179 | -------------------------------------------------------------------------------- /ios/HybridDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 48 | 53 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /ios/HybridDemo/HybridViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HybridViewController.swift 3 | // HybridDemo 4 | // 5 | // Created by wc on 13/08/2017. 6 | // Copyright © 2017 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import WebKit 13 | import SnapKit 14 | import SwiftyJSON 15 | 16 | extension WKWebView { 17 | 18 | var parentViewController: HybridViewController? { 19 | var parentResponder: UIResponder? = self 20 | while parentResponder != nil { 21 | parentResponder = parentResponder!.next 22 | if let viewController = parentResponder as? HybridViewController { 23 | return viewController 24 | } 25 | } 26 | return nil 27 | } 28 | 29 | } 30 | 31 | public protocol HybridPlugin { 32 | 33 | static var name: String { get } 34 | 35 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable 36 | 37 | } 38 | 39 | class ScriptMessageHandler: NSObject, WKScriptMessageHandler { 40 | 41 | let subject = PublishSubject() 42 | let disposeBag = DisposeBag() 43 | 44 | func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { 45 | subject.onNext(message.body) 46 | } 47 | 48 | } 49 | 50 | typealias Callback = () -> () 51 | 52 | public class ScriptMessageService { 53 | 54 | static var plugins: [HybridPlugin.Type] = [] 55 | 56 | } 57 | 58 | class Hybrid { 59 | 60 | static let shared = Hybrid() 61 | 62 | var webView: WKWebView! 63 | 64 | private let disposeBag = DisposeBag() 65 | 66 | init() { 67 | 68 | let configuration = WKWebViewConfiguration() 69 | 70 | let userContentController = WKUserContentController() 71 | for plugin in ScriptMessageService.plugins { 72 | let scriptMessageHandler = ScriptMessageHandler() 73 | let receive = scriptMessageHandler.subject 74 | .flatMap { [weak self] (message) -> Observable<(message: JSON, webView: WKWebView)> in 75 | guard let `self` = self else { 76 | return Observable.empty() 77 | } 78 | return Observable.just((message: JSON(message), webView: self.webView)) 79 | } 80 | plugin.didReceive(message: receive) 81 | .disposed(by: scriptMessageHandler.disposeBag) 82 | userContentController.add(scriptMessageHandler, name: plugin.name) 83 | } 84 | configuration.userContentController = userContentController 85 | 86 | let preferences = WKPreferences() 87 | preferences.javaScriptEnabled = true 88 | preferences.javaScriptCanOpenWindowsAutomatically = true 89 | configuration.preferences = preferences 90 | 91 | let webView = WKWebView(frame: CGRect.zero, configuration: configuration) 92 | 93 | if #available(iOS 11.0, *) { 94 | webView.scrollView.contentInsetAdjustmentBehavior = .never 95 | } 96 | 97 | // let rc = UIRefreshControl() 98 | // rc.tintColor = UIColor.black 99 | // webView.scrollView.refreshControl = rc 100 | 101 | self.webView = webView 102 | 103 | } 104 | 105 | func preLoad(url: URL) { 106 | self.webView.load(URLRequest(url: url)) 107 | } 108 | 109 | } 110 | 111 | extension UIView { 112 | 113 | var imageSnapshot: UIImage { 114 | UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, UIScreen.main.scale) 115 | self.drawHierarchy(in: self.bounds, afterScreenUpdates: true) 116 | let image = UIGraphicsGetImageFromCurrentImageContext() 117 | UIGraphicsEndImageContext() 118 | return image! 119 | } 120 | 121 | } 122 | 123 | class HybridViewController: UIViewController { 124 | 125 | var webView: WKWebView! 126 | 127 | let snapshotImageView = UIImageView() 128 | 129 | let disposeBag = DisposeBag() 130 | 131 | override func viewDidLoad() { 132 | super.viewDidLoad() 133 | 134 | // snapshotImageView.isUserInteractionEnabled = true 135 | snapshotImageView.isHidden = true 136 | 137 | view.backgroundColor = UIColor.white 138 | 139 | self.view.addSubview(Hybrid.shared.webView) 140 | self.webView = Hybrid.shared.webView 141 | 142 | self.webView.snp.makeConstraints { (make) in 143 | make.edges.equalTo(self.view) 144 | } 145 | 146 | self.view.addSubview(snapshotImageView) 147 | self.snapshotImageView.snp.makeConstraints { (make) in 148 | make.edges.equalTo(self.view) 149 | } 150 | 151 | let navigationController = self.navigationController 152 | 153 | if let edgePanGestureRecognizer = self.navigationController?.interactivePopGestureRecognizer as? UIScreenEdgePanGestureRecognizer { 154 | edgePanGestureRecognizer.rx.event 155 | .filter { $0.state == UIGestureRecognizerState.ended } 156 | .map { [unowned self] (edgePanGestureRecognizer) -> Bool in 157 | guard let view = edgePanGestureRecognizer.view else { 158 | fatalError() 159 | } 160 | let translatedPoint = edgePanGestureRecognizer.translation(in: view) 161 | return !navigationController!.viewControllers.contains(self) && (translatedPoint.x > self.view.bounds.size.width * 0.5 || edgePanGestureRecognizer.velocity(in: self.view).x > 500) 162 | } 163 | .subscribe(onNext: { [weak self, weak navigationController] pop in 164 | print(pop) // pop 执行了两次 165 | if pop { 166 | let imageSnapshot = self?.webView.imageSnapshot 167 | Hybrid.shared.webView.evaluateJavaScript("window.history.back();", completionHandler: { _ in 168 | if let navigationController = navigationController { 169 | self?.snapshotImageView.isHidden = false 170 | self?.snapshotImageView.image = imageSnapshot 171 | if let pre = navigationController.viewControllers[navigationController.viewControllers.count - 1] as? HybridViewController { 172 | pre.view.addSubview(Hybrid.shared.webView) 173 | pre.view.insertSubview(Hybrid.shared.webView, belowSubview: pre.snapshotImageView) 174 | pre.webView.snp.remakeConstraints({ (make) in 175 | make.edges.equalTo(pre.view) 176 | }) 177 | // pre.snapshotImageView.isHidden = true 178 | // pre.snapshotImageView.image = nil 179 | } 180 | 181 | } 182 | }) 183 | } 184 | }) 185 | .disposed(by: disposeBag) 186 | } 187 | 188 | 189 | } 190 | 191 | // override func viewWillAppear(_ animated: Bool) { 192 | // super.viewWillAppear(animated) 193 | // self.view.addSubview(Hybrid.shared.webView) 194 | // self.view.insertSubview(Hybrid.shared.webView, belowSubview: self.snapshotImageView) 195 | // self.webView = Hybrid.shared.webView 196 | // 197 | // self.webView.snp.remakeConstraints { (make) in 198 | // make.edges.equalTo(self.view) 199 | // } 200 | // } 201 | // 202 | override func viewDidAppear(_ animated: Bool) { 203 | super.viewDidAppear(animated) 204 | if !self.snapshotImageView.isHidden { 205 | self.snapshotImageView.isHidden = true 206 | self.snapshotImageView.image = nil 207 | } 208 | } 209 | 210 | // 211 | // override func viewWillDisappear(_ animated: Bool) { 212 | // super.viewWillDisappear(animated) 213 | // self.snapshotImageView.isHidden = false 214 | // if self.snapshotImageView.image == nil { 215 | // self.snapshotImageView.image = self.webView.imageSnapshot 216 | // } 217 | // } 218 | 219 | override func navigationShouldPopOnBackButton() -> Bool { 220 | // if self.webView.canGoBack { 221 | // self.webView.evaluateJavaScript("window.history.back();", completionHandler: nil) 222 | // return false 223 | // } else { 224 | // return true 225 | // } 226 | self.snapshotImageView.isHidden = false 227 | self.snapshotImageView.image = self.webView.imageSnapshot 228 | self.webView.evaluateJavaScript("window.history.back();", completionHandler: nil) 229 | if let pre = self.navigationController!.viewControllers[self.navigationController!.viewControllers.count - 2] as? HybridViewController { 230 | pre.view.addSubview(Hybrid.shared.webView) 231 | pre.view.insertSubview(Hybrid.shared.webView, belowSubview: pre.snapshotImageView) 232 | 233 | pre.snapshotImageView.image = nil 234 | pre.snapshotImageView.isHidden = true 235 | 236 | pre.webView.snp.remakeConstraints({ (make) in 237 | make.edges.equalTo(pre.view) 238 | }) 239 | } 240 | return true 241 | } 242 | 243 | } 244 | 245 | -------------------------------------------------------------------------------- /ios/HybridDemo/Plugin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plugin.swift 3 | // HybridDemo 4 | // 5 | // Created by wc on 13/08/2017. 6 | // Copyright © 2017 DianQK. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | import WebKit 13 | import SnapKit 14 | import SwiftyJSON 15 | 16 | struct TitlePlugin: HybridPlugin { 17 | 18 | static var name: String { 19 | return "title" 20 | } 21 | 22 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 23 | return message 24 | .subscribe(onNext: { (message, webView) in 25 | let title = message["title"].string 26 | if webView.parentViewController?.title != title { 27 | webView.parentViewController?.title = title 28 | } 29 | }) 30 | } 31 | 32 | } 33 | 34 | public protocol CallBackHybridPlugin: HybridPlugin { 35 | 36 | static func didReceive(message: JSON, webView: WKWebView) -> Observable 37 | 38 | } 39 | 40 | extension CallBackHybridPlugin { 41 | 42 | public static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 43 | return message 44 | .flatMap { (message, webView) -> Observable<(callbackId: String, response: JSON, webView: WKWebView)> in 45 | let callbackId = message["callbackId"].stringValue 46 | let content = message["content"] 47 | return didReceive(message: content, webView: webView) 48 | .catchError { (error) -> Observable in 49 | return Observable.just(JSON(["error": ["code": error._code, "message": error.localizedDescription]])) 50 | } 51 | .map { (response) in 52 | return (callbackId: callbackId, response: response, webView: webView) 53 | } 54 | } 55 | .observeOn(MainScheduler.instance) 56 | .subscribe(onNext: { (callbackId, response, webView) in 57 | webView.evaluateJavaScript("window.$native.callbacks['\(callbackId)'].callback(\(response.rawString() ?? "{}"));", completionHandler: nil) 58 | }) 59 | } 60 | 61 | } 62 | 63 | 64 | struct SelectImagePlugin: CallBackHybridPlugin { 65 | 66 | static var name: String { 67 | return "selectImage" 68 | } 69 | 70 | static func didReceive(message: JSON, webView: WKWebView) -> Observable { 71 | return Observable 72 | .create { (observer) -> Disposable in 73 | let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 74 | alert.addAction(UIAlertAction(title: "拍照", style: .default, handler: { _ in 75 | observer.onNext(UIImagePickerControllerSourceType.camera) 76 | observer.onCompleted() 77 | })) 78 | alert.addAction(UIAlertAction(title: "从相册选择", style: .default, handler: { _ in 79 | observer.onNext(UIImagePickerControllerSourceType.photoLibrary) 80 | observer.onCompleted() 81 | })) 82 | alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: nil)) 83 | webView.parentViewController?.present(alert, animated: true, completion: nil) 84 | return Disposables.create { 85 | alert.dismiss(animated: true, completion: nil) 86 | } 87 | } 88 | .flatMap { sourceType in 89 | UIImagePickerController.rx.createWithParent(webView.parentViewController!) { picker in 90 | picker.sourceType = sourceType 91 | picker.allowsEditing = true 92 | } 93 | } 94 | .flatMap { $0.rx.didFinishPickingMediaWithInfo } 95 | .take(1) 96 | .map { return $0[UIImagePickerControllerEditedImage] as! UIImage } 97 | .map { UIImagePNGRepresentation($0)!.base64EncodedString() } 98 | .map { return JSON(["image": "data:img/jpg;base64," + $0]) } 99 | } 100 | 101 | } 102 | 103 | struct RightBarTitlePlugin: HybridPlugin { 104 | 105 | static var name: String { 106 | return "rightBarTitle" 107 | } 108 | 109 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 110 | return message 111 | .flatMapLatest { (message, webView) -> Observable in 112 | let title = message["title"].stringValue 113 | if title.isEmpty { 114 | webView.parentViewController?.navigationItem.rightBarButtonItem = nil 115 | return Observable.empty() 116 | } 117 | let bar: UIBarButtonItem 118 | if let rightBarButtonItem = webView.parentViewController?.navigationItem.rightBarButtonItem { 119 | bar = rightBarButtonItem 120 | if bar.title != title { 121 | webView.parentViewController?.navigationItem.rightBarButtonItem = nil 122 | bar.title = title 123 | webView.parentViewController?.navigationItem.rightBarButtonItem = bar 124 | } 125 | } else { 126 | bar = UIBarButtonItem(title: title, style: UIBarButtonItemStyle.plain, target: nil, action: nil) 127 | webView.parentViewController?.navigationItem.rightBarButtonItem = bar 128 | } 129 | return bar.rx.tap.map { webView } 130 | } 131 | .subscribe(onNext: { (webView) in 132 | webView.evaluateJavaScript("window.$native.rightBarClick();", completionHandler: nil) 133 | }) 134 | } 135 | 136 | } 137 | 138 | struct LogPlugin: HybridPlugin { 139 | 140 | static var name: String { 141 | return "log" 142 | } 143 | 144 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 145 | return message 146 | .subscribe(onNext: { (message, webView) in 147 | print(message) 148 | }) 149 | } 150 | 151 | } 152 | 153 | struct DisplayImagePlugin: CallBackHybridPlugin { 154 | 155 | static var name: String { 156 | return "displayImage" 157 | } 158 | 159 | static func didReceive(message: JSON, webView: WKWebView) -> Observable { 160 | guard let image = URL(string: message["image"].stringValue).flatMap({ try? Data(contentsOf: $0) }).flatMap({ UIImage(data: $0) }) else { 161 | return Observable.just(JSON([:])) 162 | } 163 | let keyWindow = UIApplication.shared.keyWindow! 164 | let frame = CGRect( 165 | x: message["x"].doubleValue, 166 | y: message["y"].doubleValue + Double(-webView.convert(webView.bounds.origin, from: keyWindow).y) - Double(webView.scrollView.contentOffset.y), 167 | width: message["width"].doubleValue, 168 | height: message["height"].doubleValue 169 | ) 170 | let displayView = DisplayView(frame: keyWindow.bounds) 171 | displayView.display(image: image, frame: frame) 172 | return displayView.displayFinished.ifEmpty(default: ()).map { JSON([:]) } 173 | } 174 | 175 | private class DisplayView: UIView { 176 | 177 | let imageView = UIImageView() 178 | let disposeBag = DisposeBag() 179 | let visualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: UIBlurEffectStyle.light)) 180 | 181 | var originFrame = CGRect.zero 182 | 183 | override init(frame: CGRect) { 184 | super.init(frame: frame) 185 | 186 | self.addSubview(visualEffectView) 187 | visualEffectView.frame = self.bounds 188 | self.visualEffectView.alpha = 0 189 | 190 | self.addSubview(imageView) 191 | self.isUserInteractionEnabled = true 192 | let tap = UITapGestureRecognizer() 193 | tap.rx.event 194 | .subscribe(onNext: { [weak self] _ in 195 | UIView.animate(withDuration: 0.3, animations: { 196 | if let `self` = self { 197 | self.visualEffectView.alpha = 0 198 | self.imageView.frame = self.originFrame 199 | } 200 | }, completion: { _ in 201 | self?.displayFinished.onNext(()) 202 | }) 203 | }) 204 | .disposed(by: disposeBag) 205 | self.addGestureRecognizer(tap) 206 | self.displayFinished.debounce(0.1, scheduler: MainScheduler.asyncInstance) 207 | .subscribe(onNext: { [weak self] in 208 | self?.removeFromSuperview() 209 | }) 210 | .disposed(by: disposeBag) 211 | } 212 | 213 | required init?(coder aDecoder: NSCoder) { 214 | fatalError("init(coder:) has not been implemented") 215 | } 216 | 217 | func display(image: UIImage, frame: CGRect) { 218 | self.originFrame = frame 219 | self.imageView.image = image 220 | self.imageView.frame = frame 221 | let view = UIApplication.shared.keyWindow! 222 | view.addSubview(self) 223 | UIView.animate(withDuration: 0.3, animations: { 224 | self.visualEffectView.alpha = 1 225 | let height = view.bounds.width / frame.width * frame.height 226 | let y = (view.bounds.height - height) / 2 227 | self.imageView.frame = CGRect(x: 0, y: y, width: view.bounds.width, height: height) 228 | }) 229 | } 230 | 231 | let displayFinished = PublishSubject<()>() 232 | } 233 | 234 | } 235 | 236 | struct HTTPRequestPlugin: CallBackHybridPlugin { 237 | 238 | static var name: String { 239 | return "http" 240 | } 241 | 242 | static func didReceive(message: JSON, webView: WKWebView) -> Observable { 243 | let query: JSON = message["query"] 244 | return URLSession.shared.rx.json(url: URL(string: "https://httpbin.org/get?\(query.map { "\($0)=\($1.stringValue)" }.joined(separator: "&"))")!) 245 | .map { JSON($0) } 246 | } 247 | 248 | } 249 | 250 | import MBProgressHUD 251 | 252 | struct LoadingPlugin: HybridPlugin { 253 | 254 | static var name: String { 255 | return "loading" 256 | } 257 | 258 | static let hud: MBProgressHUD = { 259 | let hud = MBProgressHUD(view: UIApplication.shared.keyWindow!) 260 | hud.removeFromSuperViewOnHide = true 261 | return hud 262 | }() 263 | 264 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 265 | return message 266 | .subscribe(onNext: { (message, webView) in 267 | let loading = message["content"].boolValue 268 | if loading { 269 | UIApplication.shared.keyWindow!.addSubview(hud) 270 | hud.show(animated: true) 271 | } else { 272 | hud.hide(animated: true) 273 | } 274 | }) 275 | } 276 | 277 | } 278 | 279 | struct ToastPlugin: HybridPlugin { 280 | 281 | static var name: String { 282 | return "toast" 283 | } 284 | 285 | static let hud: MBProgressHUD = { 286 | let hud = MBProgressHUD(view: UIApplication.shared.keyWindow!) 287 | hud.mode = MBProgressHUDMode.text 288 | return hud 289 | }() 290 | 291 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 292 | return message 293 | .subscribe(onNext: { (message, webView) in 294 | let text = message["content"].stringValue 295 | hud.label.text = text 296 | UIApplication.shared.keyWindow!.addSubview(hud) 297 | hud.show(animated: true) 298 | hud.hide(animated: true, afterDelay: 1) 299 | }) 300 | } 301 | 302 | } 303 | 304 | struct NavigationPlugin: HybridPlugin { 305 | 306 | static var name: String { 307 | return "navigation" 308 | } 309 | 310 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 311 | return message 312 | .subscribe(onNext: { (message, webView) in 313 | let title = message["content"]["title"].stringValue 314 | let nextHybrid = R.storyboard.main.hybridViewController()! 315 | nextHybrid.title = title 316 | let parentViewController = webView.parentViewController 317 | parentViewController?.snapshotImageView.image = webView.imageSnapshot 318 | parentViewController?.snapshotImageView.isHidden = false 319 | webView.parentViewController?.navigationController?.pushViewController(nextHybrid, animated: true) 320 | }) 321 | } 322 | 323 | } 324 | 325 | struct NavigationGoPlugin: HybridPlugin { 326 | 327 | static var name: String { // 当前只支持负数 go(-2) 328 | return "go" 329 | } 330 | 331 | static func didReceive(message: Observable<(message: JSON, webView: WKWebView)>) -> Disposable { 332 | return message 333 | .subscribe(onNext: { (message, webView) in 334 | guard let navigationController = webView.parentViewController?.navigationController else { return } 335 | let n = abs(message["content"].intValue) 336 | if n < navigationController.viewControllers.count { 337 | let to = navigationController.viewControllers[navigationController.viewControllers.count - n - 1] 338 | webView.parentViewController?.snapshotImageView.isHidden = false 339 | webView.parentViewController?.snapshotImageView.image = webView.parentViewController?.webView.imageSnapshot 340 | if let to = to as? HybridViewController { 341 | to.view.addSubview(Hybrid.shared.webView) 342 | to.view.insertSubview(Hybrid.shared.webView, belowSubview: to.snapshotImageView) 343 | 344 | to.snapshotImageView.image = nil 345 | to.snapshotImageView.isHidden = true 346 | 347 | to.webView.snp.remakeConstraints({ (make) in 348 | make.edges.equalTo(to.view) 349 | }) 350 | } 351 | navigationController.popToViewController(to, animated: true) 352 | } 353 | }) 354 | } 355 | 356 | } 357 | -------------------------------------------------------------------------------- /ios/HybridDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5645E48D1F40844600CCCF19 /* Plugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5645E48C1F40844600CCCF19 /* Plugin.swift */; }; 11 | 5645E48F1F409E1D00CCCF19 /* UIImagePickerController+RxCreate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5645E48E1F409E1D00CCCF19 /* UIImagePickerController+RxCreate.swift */; }; 12 | 5645E4911F409E9500CCCF19 /* UIImagePickerController+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5645E4901F409E9500CCCF19 /* UIImagePickerController+Rx.swift */; }; 13 | 5687A06E1F4075E300776F69 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5687A06D1F4075E300776F69 /* AppDelegate.swift */; }; 14 | 5687A0701F4075E300776F69 /* HybridViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5687A06F1F4075E300776F69 /* HybridViewController.swift */; }; 15 | 5687A0731F4075E300776F69 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5687A0711F4075E300776F69 /* Main.storyboard */; }; 16 | 5687A0751F4075E300776F69 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5687A0741F4075E300776F69 /* Assets.xcassets */; }; 17 | 5687A0781F4075E300776F69 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5687A0761F4075E300776F69 /* LaunchScreen.storyboard */; }; 18 | 56B90A461F433A4500A8F327 /* vue.zip in Resources */ = {isa = PBXBuildFile; fileRef = 56B90A451F433A4500A8F327 /* vue.zip */; }; 19 | 56B90A491F433AAA00A8F327 /* R.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B90A481F433AAA00A8F327 /* R.generated.swift */; }; 20 | 56B90A4B1F43482E00A8F327 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B90A4A1F43482E00A8F327 /* HomeViewController.swift */; }; 21 | 56B90A4D1F434C5E00A8F327 /* react.zip in Resources */ = {isa = PBXBuildFile; fileRef = 56B90A4C1F434C5E00A8F327 /* react.zip */; }; 22 | 56B90A511F434E2300A8F327 /* UIViewController+BackButtonHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 56B90A501F434E2300A8F327 /* UIViewController+BackButtonHandler.m */; }; 23 | E44F2D2511D99408424AFF44 /* Pods_HybridDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C28706D5C85B6D35F197AB11 /* Pods_HybridDemo.framework */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXFileReference section */ 27 | 224D9A72C7D1C6EDC8115FF5 /* Pods-HybridDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HybridDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-HybridDemo/Pods-HybridDemo.release.xcconfig"; sourceTree = ""; }; 28 | 5645E48C1F40844600CCCF19 /* Plugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Plugin.swift; sourceTree = ""; }; 29 | 5645E48E1F409E1D00CCCF19 /* UIImagePickerController+RxCreate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImagePickerController+RxCreate.swift"; sourceTree = ""; }; 30 | 5645E4901F409E9500CCCF19 /* UIImagePickerController+Rx.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImagePickerController+Rx.swift"; sourceTree = ""; }; 31 | 5687A06A1F4075E300776F69 /* HybridDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HybridDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 5687A06D1F4075E300776F69 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 5687A06F1F4075E300776F69 /* HybridViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HybridViewController.swift; sourceTree = ""; }; 34 | 5687A0721F4075E300776F69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 35 | 5687A0741F4075E300776F69 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 5687A0771F4075E300776F69 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 5687A0791F4075E300776F69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 56B90A451F433A4500A8F327 /* vue.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = vue.zip; sourceTree = ""; }; 39 | 56B90A481F433AAA00A8F327 /* R.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = R.generated.swift; sourceTree = ""; }; 40 | 56B90A4A1F43482E00A8F327 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 41 | 56B90A4C1F434C5E00A8F327 /* react.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = react.zip; sourceTree = ""; }; 42 | 56B90A4E1F434E2200A8F327 /* HybridDemo-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "HybridDemo-Bridging-Header.h"; sourceTree = ""; }; 43 | 56B90A4F1F434E2300A8F327 /* UIViewController+BackButtonHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+BackButtonHandler.h"; sourceTree = ""; }; 44 | 56B90A501F434E2300A8F327 /* UIViewController+BackButtonHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+BackButtonHandler.m"; sourceTree = ""; }; 45 | 65BEB6E632B013A915B9F43F /* Pods-HybridDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-HybridDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-HybridDemo/Pods-HybridDemo.debug.xcconfig"; sourceTree = ""; }; 46 | C28706D5C85B6D35F197AB11 /* Pods_HybridDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HybridDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 5687A0671F4075E300776F69 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | E44F2D2511D99408424AFF44 /* Pods_HybridDemo.framework in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 4F9646C344F1DE74E08CB1DA /* Pods */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 65BEB6E632B013A915B9F43F /* Pods-HybridDemo.debug.xcconfig */, 65 | 224D9A72C7D1C6EDC8115FF5 /* Pods-HybridDemo.release.xcconfig */, 66 | ); 67 | name = Pods; 68 | sourceTree = ""; 69 | }; 70 | 5687A0611F4075E300776F69 = { 71 | isa = PBXGroup; 72 | children = ( 73 | 5687A06C1F4075E300776F69 /* HybridDemo */, 74 | 5687A06B1F4075E300776F69 /* Products */, 75 | 4F9646C344F1DE74E08CB1DA /* Pods */, 76 | C0A8AB17BED2259D2879176D /* Frameworks */, 77 | ); 78 | sourceTree = ""; 79 | }; 80 | 5687A06B1F4075E300776F69 /* Products */ = { 81 | isa = PBXGroup; 82 | children = ( 83 | 5687A06A1F4075E300776F69 /* HybridDemo.app */, 84 | ); 85 | name = Products; 86 | sourceTree = ""; 87 | }; 88 | 5687A06C1F4075E300776F69 /* HybridDemo */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 56B90A4F1F434E2300A8F327 /* UIViewController+BackButtonHandler.h */, 92 | 56B90A501F434E2300A8F327 /* UIViewController+BackButtonHandler.m */, 93 | 56B90A481F433AAA00A8F327 /* R.generated.swift */, 94 | 56B90A4C1F434C5E00A8F327 /* react.zip */, 95 | 56B90A451F433A4500A8F327 /* vue.zip */, 96 | 5645E4901F409E9500CCCF19 /* UIImagePickerController+Rx.swift */, 97 | 5645E48E1F409E1D00CCCF19 /* UIImagePickerController+RxCreate.swift */, 98 | 5687A06D1F4075E300776F69 /* AppDelegate.swift */, 99 | 5687A06F1F4075E300776F69 /* HybridViewController.swift */, 100 | 5687A0711F4075E300776F69 /* Main.storyboard */, 101 | 5687A0741F4075E300776F69 /* Assets.xcassets */, 102 | 5687A0761F4075E300776F69 /* LaunchScreen.storyboard */, 103 | 5687A0791F4075E300776F69 /* Info.plist */, 104 | 5645E48C1F40844600CCCF19 /* Plugin.swift */, 105 | 56B90A4A1F43482E00A8F327 /* HomeViewController.swift */, 106 | 56B90A4E1F434E2200A8F327 /* HybridDemo-Bridging-Header.h */, 107 | ); 108 | path = HybridDemo; 109 | sourceTree = ""; 110 | }; 111 | C0A8AB17BED2259D2879176D /* Frameworks */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | C28706D5C85B6D35F197AB11 /* Pods_HybridDemo.framework */, 115 | ); 116 | name = Frameworks; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | 5687A0691F4075E300776F69 /* HybridDemo */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = 5687A07C1F4075E300776F69 /* Build configuration list for PBXNativeTarget "HybridDemo" */; 125 | buildPhases = ( 126 | 80C484EDA2C216F6FD2ADA75 /* [CP] Check Pods Manifest.lock */, 127 | 56B90A471F433A8F00A8F327 /* Run R */, 128 | 5687A0661F4075E300776F69 /* Sources */, 129 | 5687A0671F4075E300776F69 /* Frameworks */, 130 | 5687A0681F4075E300776F69 /* Resources */, 131 | 12A8845CDFFB013669B9D188 /* [CP] Embed Pods Frameworks */, 132 | B12D55EA51D0EDE4912FD321 /* [CP] Copy Pods Resources */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = HybridDemo; 139 | productName = HybridDemo; 140 | productReference = 5687A06A1F4075E300776F69 /* HybridDemo.app */; 141 | productType = "com.apple.product-type.application"; 142 | }; 143 | /* End PBXNativeTarget section */ 144 | 145 | /* Begin PBXProject section */ 146 | 5687A0621F4075E300776F69 /* Project object */ = { 147 | isa = PBXProject; 148 | attributes = { 149 | LastSwiftUpdateCheck = 0830; 150 | LastUpgradeCheck = 0900; 151 | ORGANIZATIONNAME = DianQK; 152 | TargetAttributes = { 153 | 5687A0691F4075E300776F69 = { 154 | CreatedOnToolsVersion = 8.3.3; 155 | DevelopmentTeam = RML58CN62D; 156 | LastSwiftMigration = 0830; 157 | ProvisioningStyle = Automatic; 158 | }; 159 | }; 160 | }; 161 | buildConfigurationList = 5687A0651F4075E300776F69 /* Build configuration list for PBXProject "HybridDemo" */; 162 | compatibilityVersion = "Xcode 3.2"; 163 | developmentRegion = English; 164 | hasScannedForEncodings = 0; 165 | knownRegions = ( 166 | en, 167 | Base, 168 | ); 169 | mainGroup = 5687A0611F4075E300776F69; 170 | productRefGroup = 5687A06B1F4075E300776F69 /* Products */; 171 | projectDirPath = ""; 172 | projectRoot = ""; 173 | targets = ( 174 | 5687A0691F4075E300776F69 /* HybridDemo */, 175 | ); 176 | }; 177 | /* End PBXProject section */ 178 | 179 | /* Begin PBXResourcesBuildPhase section */ 180 | 5687A0681F4075E300776F69 /* Resources */ = { 181 | isa = PBXResourcesBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | 5687A0781F4075E300776F69 /* LaunchScreen.storyboard in Resources */, 185 | 56B90A461F433A4500A8F327 /* vue.zip in Resources */, 186 | 5687A0751F4075E300776F69 /* Assets.xcassets in Resources */, 187 | 56B90A4D1F434C5E00A8F327 /* react.zip in Resources */, 188 | 5687A0731F4075E300776F69 /* Main.storyboard in Resources */, 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | }; 192 | /* End PBXResourcesBuildPhase section */ 193 | 194 | /* Begin PBXShellScriptBuildPhase section */ 195 | 12A8845CDFFB013669B9D188 /* [CP] Embed Pods Frameworks */ = { 196 | isa = PBXShellScriptBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | ); 200 | inputPaths = ( 201 | "${SRCROOT}/Pods/Target Support Files/Pods-HybridDemo/Pods-HybridDemo-frameworks.sh", 202 | "${BUILT_PRODUCTS_DIR}/MBProgressHUD/MBProgressHUD.framework", 203 | "${BUILT_PRODUCTS_DIR}/R.swift.Library/Rswift.framework", 204 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 205 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 206 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 207 | "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", 208 | ); 209 | name = "[CP] Embed Pods Frameworks"; 210 | outputPaths = ( 211 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MBProgressHUD.framework", 212 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Rswift.framework", 213 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 214 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 215 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 216 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | shellPath = /bin/sh; 220 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-HybridDemo/Pods-HybridDemo-frameworks.sh\"\n"; 221 | showEnvVarsInLog = 0; 222 | }; 223 | 56B90A471F433A8F00A8F327 /* Run R */ = { 224 | isa = PBXShellScriptBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | inputPaths = ( 229 | ); 230 | name = "Run R"; 231 | outputPaths = ( 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | shellPath = /bin/sh; 235 | shellScript = "\"$PODS_ROOT/R.swift/rswift\" generate \"$SRCROOT/HybridDemo\""; 236 | }; 237 | 80C484EDA2C216F6FD2ADA75 /* [CP] Check Pods Manifest.lock */ = { 238 | isa = PBXShellScriptBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | ); 242 | inputPaths = ( 243 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 244 | "${PODS_ROOT}/Manifest.lock", 245 | ); 246 | name = "[CP] Check Pods Manifest.lock"; 247 | outputPaths = ( 248 | "$(DERIVED_FILE_DIR)/Pods-HybridDemo-checkManifestLockResult.txt", 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | shellPath = /bin/sh; 252 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/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# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 253 | showEnvVarsInLog = 0; 254 | }; 255 | B12D55EA51D0EDE4912FD321 /* [CP] Copy Pods Resources */ = { 256 | isa = PBXShellScriptBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | inputPaths = ( 261 | ); 262 | name = "[CP] Copy Pods Resources"; 263 | outputPaths = ( 264 | ); 265 | runOnlyForDeploymentPostprocessing = 0; 266 | shellPath = /bin/sh; 267 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-HybridDemo/Pods-HybridDemo-resources.sh\"\n"; 268 | showEnvVarsInLog = 0; 269 | }; 270 | /* End PBXShellScriptBuildPhase section */ 271 | 272 | /* Begin PBXSourcesBuildPhase section */ 273 | 5687A0661F4075E300776F69 /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 5645E48D1F40844600CCCF19 /* Plugin.swift in Sources */, 278 | 5645E48F1F409E1D00CCCF19 /* UIImagePickerController+RxCreate.swift in Sources */, 279 | 56B90A511F434E2300A8F327 /* UIViewController+BackButtonHandler.m in Sources */, 280 | 5687A0701F4075E300776F69 /* HybridViewController.swift in Sources */, 281 | 5645E4911F409E9500CCCF19 /* UIImagePickerController+Rx.swift in Sources */, 282 | 5687A06E1F4075E300776F69 /* AppDelegate.swift in Sources */, 283 | 56B90A4B1F43482E00A8F327 /* HomeViewController.swift in Sources */, 284 | 56B90A491F433AAA00A8F327 /* R.generated.swift in Sources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXSourcesBuildPhase section */ 289 | 290 | /* Begin PBXVariantGroup section */ 291 | 5687A0711F4075E300776F69 /* Main.storyboard */ = { 292 | isa = PBXVariantGroup; 293 | children = ( 294 | 5687A0721F4075E300776F69 /* Base */, 295 | ); 296 | name = Main.storyboard; 297 | sourceTree = ""; 298 | }; 299 | 5687A0761F4075E300776F69 /* LaunchScreen.storyboard */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | 5687A0771F4075E300776F69 /* Base */, 303 | ); 304 | name = LaunchScreen.storyboard; 305 | sourceTree = ""; 306 | }; 307 | /* End PBXVariantGroup section */ 308 | 309 | /* Begin XCBuildConfiguration section */ 310 | 5687A07A1F4075E300776F69 /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 321 | CLANG_WARN_BOOL_CONVERSION = YES; 322 | CLANG_WARN_COMMA = YES; 323 | CLANG_WARN_CONSTANT_CONVERSION = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INFINITE_RECURSION = YES; 329 | CLANG_WARN_INT_CONVERSION = YES; 330 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = dwarf; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | ENABLE_TESTABILITY = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PREPROCESSOR_DEFINITIONS = ( 348 | "DEBUG=1", 349 | "$(inherited)", 350 | ); 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 358 | MTL_ENABLE_DEBUG_INFO = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 362 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 363 | TARGETED_DEVICE_FAMILY = "1,2"; 364 | }; 365 | name = Debug; 366 | }; 367 | 5687A07B1F4075E300776F69 /* Release */ = { 368 | isa = XCBuildConfiguration; 369 | buildSettings = { 370 | ALWAYS_SEARCH_USER_PATHS = NO; 371 | CLANG_ANALYZER_NONNULL = YES; 372 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 378 | CLANG_WARN_BOOL_CONVERSION = YES; 379 | CLANG_WARN_COMMA = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 382 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 389 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu99; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = iphoneos; 411 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 412 | TARGETED_DEVICE_FAMILY = "1,2"; 413 | VALIDATE_PRODUCT = YES; 414 | }; 415 | name = Release; 416 | }; 417 | 5687A07D1F4075E300776F69 /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | baseConfigurationReference = 65BEB6E632B013A915B9F43F /* Pods-HybridDemo.debug.xcconfig */; 420 | buildSettings = { 421 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 422 | CLANG_ENABLE_MODULES = YES; 423 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 424 | CODE_SIGN_STYLE = Automatic; 425 | DEVELOPMENT_TEAM = RML58CN62D; 426 | INFOPLIST_FILE = HybridDemo/Info.plist; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 428 | PRODUCT_BUNDLE_IDENTIFIER = org.dianqk.org.HybridDemo; 429 | PRODUCT_NAME = "$(TARGET_NAME)"; 430 | PROVISIONING_PROFILE_SPECIFIER = ""; 431 | SWIFT_OBJC_BRIDGING_HEADER = "HybridDemo/HybridDemo-Bridging-Header.h"; 432 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 433 | SWIFT_VERSION = 3.0; 434 | }; 435 | name = Debug; 436 | }; 437 | 5687A07E1F4075E300776F69 /* Release */ = { 438 | isa = XCBuildConfiguration; 439 | baseConfigurationReference = 224D9A72C7D1C6EDC8115FF5 /* Pods-HybridDemo.release.xcconfig */; 440 | buildSettings = { 441 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 442 | CLANG_ENABLE_MODULES = YES; 443 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 444 | CODE_SIGN_STYLE = Automatic; 445 | DEVELOPMENT_TEAM = RML58CN62D; 446 | INFOPLIST_FILE = HybridDemo/Info.plist; 447 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 448 | PRODUCT_BUNDLE_IDENTIFIER = org.dianqk.org.HybridDemo; 449 | PRODUCT_NAME = "$(TARGET_NAME)"; 450 | PROVISIONING_PROFILE_SPECIFIER = ""; 451 | SWIFT_OBJC_BRIDGING_HEADER = "HybridDemo/HybridDemo-Bridging-Header.h"; 452 | SWIFT_VERSION = 3.0; 453 | }; 454 | name = Release; 455 | }; 456 | /* End XCBuildConfiguration section */ 457 | 458 | /* Begin XCConfigurationList section */ 459 | 5687A0651F4075E300776F69 /* Build configuration list for PBXProject "HybridDemo" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | 5687A07A1F4075E300776F69 /* Debug */, 463 | 5687A07B1F4075E300776F69 /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 5687A07C1F4075E300776F69 /* Build configuration list for PBXNativeTarget "HybridDemo" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 5687A07D1F4075E300776F69 /* Debug */, 472 | 5687A07E1F4075E300776F69 /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | /* End XCConfigurationList section */ 478 | }; 479 | rootObject = 5687A0621F4075E300776F69 /* Project object */; 480 | } 481 | --------------------------------------------------------------------------------