Skip to content

Commit 35abfba

Browse files
committed
Support Combine
1 parent 054a3cf commit 35abfba

2 files changed

Lines changed: 149 additions & 3 deletions

File tree

Demo/Demo/Preview1ViewController.swift

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import UIKit
99
import StackKit
10+
import Combine
1011

1112
class Preview1ViewController: UIViewController {
1213

@@ -15,6 +16,11 @@ class Preview1ViewController: UIViewController {
1516
let briefLabel = UILabel()
1617

1718
let container = HStackView(alignment: .center)
19+
20+
@Published var name = "iWECon/StackKit"
21+
@Published var brief = "The best way to use HStack and VStack in UIKit, and also supports Spacer and Divider."
22+
23+
var cancellables: Set<AnyCancellable> = []
1824

1925
override func viewDidLoad() {
2026
super.viewDidLoad()
@@ -25,11 +31,11 @@ class Preview1ViewController: UIViewController {
2531
logoView.layer.cornerCurve = .continuous
2632
logoView.backgroundColor = .systemGroupedBackground
2733

28-
nameLabel.text = "iWECon/StackKit"
34+
nameLabel.text = name
2935
nameLabel.font = .systemFont(ofSize: 14, weight: .medium)
3036
nameLabel.textColor = .black
3137

32-
briefLabel.text = "The best way to use HStack and VStack in UIKit, and also supports Spacer and Divider."
38+
briefLabel.text = brief
3339
briefLabel.font = .systemFont(ofSize: 12, weight: .light)
3440
briefLabel.textColor = .black
3541
briefLabel.numberOfLines = 0
@@ -45,12 +51,30 @@ class Preview1ViewController: UIViewController {
4551
container.addContent {
4652
logoView.stack.size(80)
4753
VStackView(alignment: .left, distribution: .spacing(6)) {
48-
nameLabel
54+
nameLabel.stack
55+
.receive(text: $name, storeIn: &cancellables)
56+
4957
briefLabel.stack.maxWidth(220)
58+
.receive(text: $brief, storeIn: &cancellables)
5059
}
5160
}
5261

5362
self.view.addSubview(container)
63+
64+
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 2) {
65+
print("done")
66+
self.name = "StackKit design by iWECon"
67+
self.brief = "Yes!!!"
68+
self.container.setNeedsLayout()
69+
}
70+
71+
DispatchQueue.global().asyncAfter(wallDeadline: .now() + 5) {
72+
self.name = "StackKit design by iWECon1"
73+
self.brief = "Yes!!!2"
74+
DispatchQueue.main.async {
75+
self.container.setNeedsLayout()
76+
}
77+
}
5478
}
5579

5680
override func viewDidLayoutSubviews() {
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//
2+
// File.swift
3+
//
4+
//
5+
// Created by bro on 2022/11/22.
6+
//
7+
8+
import UIKit
9+
import Combine
10+
11+
fileprivate func safetyAccessUI(_ closure: @escaping () -> Void) {
12+
if Thread.isMainThread {
13+
closure()
14+
} else {
15+
DispatchQueue.main.async {
16+
closure()
17+
}
18+
}
19+
}
20+
21+
@available(iOS 13.0, *)
22+
extension StackKitCompatible where Base: UIView {
23+
24+
@discardableResult
25+
public func receive<Value>(
26+
publisher: Published<Value>.Publisher,
27+
storeIn cancellables: inout Set<AnyCancellable>,
28+
sink receiveValue: @escaping ((Base, Published<Value>.Publisher.Output) -> Void)
29+
) -> Self
30+
{
31+
let v = self.view
32+
publisher.sink { [weak v] output in
33+
guard let v else { return }
34+
receiveValue(v, output)
35+
}.store(in: &cancellables)
36+
return self
37+
}
38+
39+
@discardableResult
40+
public func receive(
41+
isHidden publisher: Published<Bool>.Publisher,
42+
storeIn cancellables: inout Set<AnyCancellable>
43+
) -> Self
44+
{
45+
receive(publisher: publisher, storeIn: &cancellables) { view, output in
46+
safetyAccessUI {
47+
view.isHidden = output
48+
}
49+
}
50+
return self
51+
}
52+
}
53+
54+
55+
/**
56+
这里由于设计逻辑,会有个问题
57+
58+
系统提供的 receive(on: DispatchQueue.main) 虽然也可以
59+
⚠️ 但是:DispatchQueue 是个调度器
60+
任务添加后需要等到下一个 loop cycle 才会执行
61+
这样就会导致一个问题:
62+
❌ 在主线程中修改值,并触发 `container.setNeedsLayout()` 的时候,
63+
`setNeedsLayout` 会先执行,而 `publisher` 会将任务派发到下一个 loop cycle (也就是 setNeedsLayout 和 receive 先后执行的问题)
64+
*/
65+
66+
@available(iOS 13.0, *)
67+
extension StackKitCompatible where Base: UILabel {
68+
69+
@discardableResult
70+
public func receive(
71+
text publisher: Published<String>.Publisher,
72+
storeIn cancellables: inout Set<AnyCancellable>
73+
) -> Self
74+
{
75+
receive(publisher: publisher, storeIn: &cancellables) { view, output in
76+
safetyAccessUI {
77+
view.text = output
78+
}
79+
}
80+
}
81+
82+
83+
@discardableResult
84+
public func receive(
85+
text publisher: Published<String?>.Publisher,
86+
storeIn cancellables: inout Set<AnyCancellable>
87+
) -> Self
88+
{
89+
receive(publisher: publisher, storeIn: &cancellables) { view, output in
90+
safetyAccessUI {
91+
view.text = output
92+
}
93+
}
94+
}
95+
96+
@discardableResult
97+
public func receive(
98+
attributedText publisher: Published<NSAttributedString>.Publisher,
99+
storeIn cancellables: inout Set<AnyCancellable>
100+
) -> Self
101+
{
102+
receive(publisher: publisher, storeIn: &cancellables) { view, output in
103+
safetyAccessUI {
104+
view.attributedText = output
105+
}
106+
}
107+
}
108+
109+
@discardableResult
110+
public func receive(
111+
attributedText publisher: Published<NSAttributedString?>.Publisher,
112+
storeIn cancellables: inout Set<AnyCancellable>
113+
) -> Self
114+
{
115+
receive(publisher: publisher, storeIn: &cancellables) { view, output in
116+
safetyAccessUI {
117+
view.attributedText = output
118+
}
119+
}
120+
}
121+
122+
}

0 commit comments

Comments
 (0)