GitHub
最近在 AppStore
上架了 官方 iOS App
。即使不使用 PC
,也可以处理些 GitHub
上的操作,比如组织任务、issues
反馈、评论、review
以及 merge pull requests
等。
GitHub
通知展示在收件箱中,看起来有点像邮箱的收件箱,你可以收藏或标记它们为已完成。
你也可以使用 emoji
进行评论互动,提升活跃度。
专注于Swift技术分享 – 微信公众号:SwiftMic
GitHub
最近在 AppStore
上架了 官方 iOS App
。即使不使用 PC
,也可以处理些 GitHub
上的操作,比如组织任务、issues
反馈、评论、review
以及 merge pull requests
等。
GitHub
通知展示在收件箱中,看起来有点像邮箱的收件箱,你可以收藏或标记它们为已完成。
你也可以使用 emoji
进行评论互动,提升活跃度。
本文主要介绍原生 iOS
应用如何集成 Flutter
模块。
Flutter
支持 macOS
、Windows
和 Linux
。
安装步骤可见官网:https://flutter.dev/docs/get-started/install
(注:本教程使用的是 macOS
系统)
通过 Xcode
创建一个 iOS
原生项目,命名为 Example-Flutter
。
Example-Flutter
当前目录下。Flutter
模块。flutter create --template module my_flutter
将会生成一个 my_flutter
新目录,结构如下:
my_flutter/
├─.ios/
│ ├─Runner.xcworkspace
│ └─Flutter/podhelper.rb
├─lib/
│ └─main.dart
├─test/
└─pubspec.yaml
CocoaPods
集成 Flutter
模块。在 Example-Flutter
项目目录下创建 Podfile
文件,内容编辑如下:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
use_frameworks!
flutter_application_path = './my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'Example-Flutter' do
install_all_flutter_pods(flutter_application_path)
end
然后,通过 pod install
命令集成 Flutter
模块到 iOS
工程项目中。
输出类似如下日志:
$ pod install
Analyzing dependencies
Downloading dependencies
Installing Flutter (1.0.0)
Installing FlutterPluginRegistrant (0.0.1)
Installing my_flutter (0.0.1)
Generating Pods project
Integrating client project
[!] Please close any current Xcode sessions and use `Example-Flutter.xcworkspace` for this project from now on.
Pod installation complete! There are 3 dependencies from the Podfile and 3 total pods installed.
此时,整个项目的目录结构类似是这样的。
some/path/Example-Flutter
├── Example-Flutter/
├── Example-Flutter.xcodeproj
├── Example-Flutter.xcworkspace
├── my_flutter/
│ └── .ios/
│ └── Flutter/
│ └── podhelper.rb
├── Podfile
├── Podfile.lock
└── Pods/
修改 AppDelegate.swift
文件。
import FlutterPluginRegistrant
lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
flutterEngine.run()
GeneratedPluginRegistrant.register(with: self.flutterEngine);
return true
}
然后在 iOS
原生页面上添加相应代码。
导入 Flutter
package。
import Flutter
增加一个触发按钮,代码如下:
let button = UIButton(type:UIButton.ButtonType.custom)
button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
button.setTitle("Show Flutter!", for: UIControl.State.normal)
button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
button.backgroundColor = UIColor.blue
self.view.addSubview(button)
响应 Button
点击事件,代码如下:
@objc func showFlutter() {
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController =
FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
present(flutterViewController, animated: true, completion: nil)
}
GitHub: iOS-Examples/Example-Flutter
UIView
内部包含一个 hitTest
方法,用于 UI 事件的传递。
当一个触摸事件发生时,事件传递顺序如下:
UIApplication -> UIWindow -> 寻找合适的View
假设颜色对应的 View 关系如下:
黄色:View 1
绿色:View 1_1
灰色:View 1_1_1
红色:View 1_2
View 1
定义如下:
class View1: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
print("View 1 hitTest")
return super.hitTest(point, with: event)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesBegan")
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesMoved")
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1 touchesCancelled")
}
}
同理,View11
、View111
、View12
定义类似于 View1
。
各 View
的层级关系如下:
View1 -> View11 -> View111
View1 -> View12
具体布局代码如下:
// view 1
let view1 = View1(frame: CGRect(x: 40, y: 100, width: 300, height: 400))
view1.backgroundColor = UIColor.yellow
self.view.addSubview(view1)
// view 1_1
let view11 = View11(frame: CGRect(x: 20, y: 30, width: 100, height: 120))
view11.backgroundColor = UIColor.green
view1.addSubview(view11)
// view 1_1_1
let view111 = View111(frame: CGRect(x: 10, y: 10, width: 60, height: 40))
view111.backgroundColor = UIColor.gray
view11.addSubview(view111)
// view 1_2
let view12 = View12(frame: CGRect(x: 20, y: 200, width: 100, height: 120))
view12.backgroundColor = UIColor.red
view1.addSubview(view12)
当一个触摸事件发生在灰色(View 1_1_1
)区域时,hitTest
被调用的顺序如下。
View 1 hitTest
View 1_2 hitTest
View 1_1 hitTest
View 1_1_1 hitTest
表明触摸事件发生时,先从底层 View
开始逐级向上层进行遍历,直到找到灰色视图 View111
。
寻找到合适的 View
后,将会响应对应 View
的触摸事件。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
print("View 1_1_1 touchesBegan")
}
此处合适的 View
就是 View111
,将输出日志:
View 1_1_1 touchesBegan
响应者链的传递顺序与 UI 事件传递顺序正好相反,如果当前 View
(即:View 1_1_1
)无法响应触摸事件(比如:isUserInteractionEnabled = false
),将向父一级传递事件。此时,View 1_1
将收到触摸事件,如果 View 1_1
可交互的话,将响应对应的 touchesBegan
事件。
View 1_1 touchesBegan
否则继续往父一级遍历,直到找到合适的响应对象。
GitHub: iOS-Examples/Example-View响应链
CALayer
主要负责显示内容,继承自 NSObject
。
UIView
主要对 CALayer
做了简单的封装(UIView
类中有个成员变量 layer
就是 CALayer
类型)。另外,UIView
继承自 UIResponder
类,所以也会负责处理触摸事件的响应。
UIView
部分源码如下open class UIView : UIResponder, NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace, UIFocusItem, UIFocusItemContainer, CALayerDelegate {
open class var layerClass: AnyClass { get }
public init(frame: CGRect)
public init?(coder: NSCoder)
open var isUserInteractionEnabled: Bool
open var tag: Int
open var layer: CALayer { get }
@available(iOS 9.0, *)
open var canBecomeFocused: Bool { get }
@available(iOS 9.0, *)
open var isFocused: Bool { get }
@available(iOS 9.0, *)
open var semanticContentAttribute: UISemanticContentAttribute
......
}
当对一个视图进行绘制的时候,绘图单元会向 CALayer
索取要显示元素的相关数据,此时, CALayer
会通过 delegate
通知到 UIView
,看看 UIView
是否有提供需要绘制的元素。如果 UIView
什么都不需要提供的话,就当作无视。