├── 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 |
2 |
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 |
2 |
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 |
2 |
7 |
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 |
2 |
3 |
4 |
5 |
6 |
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 |
2 |
3 | 选择图片
4 |
5 |
6 |
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 |
2 |
3 |
4 | hybrid
5 |
6 |
7 |
8 |
Send
9 |
10 |
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 |
2 |
3 |
4 |
Go
5 |
Send
6 |
7 | {{ value }}
8 |
9 |
10 |
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 |
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 |
2 |
3 |
4 |
5 |
6 | Hybrid
7 | Http
8 |
9 | Forum
10 | Chat
11 | {{count}}
12 |
13 |
14 |
15 |
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 |
2 |
9 |
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 |
26 | You need to enable JavaScript to run this app.
27 |
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://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 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
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 |
--------------------------------------------------------------------------------