Skip to content

Instantly share code, notes, and snippets.

@simonkim
Last active January 16, 2024 12:53
Show Gist options
  • Save simonkim/6d1f913f8cac20da1a9fc07c7b7391c4 to your computer and use it in GitHub Desktop.
Save simonkim/6d1f913f8cac20da1a9fc07c7b7391c4 to your computer and use it in GitHub Desktop.
Sending Event from SwiftUI View to View using Combine
import SwiftUI
import Combine
import PlaygroundSupport
struct ContentView: View {
var emitter = PassthroughSubject<(), Never>()
var body: some View {
VStack {
Text("Hello, world!")
.padding()
Button("Send Event") {
emitter.send(())
}
EventReceiver(event: emitter.eraseToAnyPublisher())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct EventReceiver: View {
var event: AnyPublisher<(), Never>
@State var count = 0
var body: some View {
Text("Received \(count)")
.onReceive(event) { _ in
count += 1
}
}
}
PlaygroundPage.current.setLiveView(ContentView())
///
/// Subscribing custom event publisher from a different view controller
///
import UIKit
import Combine
import PlaygroundSupport
class State {
@Published var counter: Int = 0
}
let state = State()
// Main View Controller: state.counter += 1
let vcMain = Tab1ViewController(state: state)
// Detail View Controller: state.assign(to: \.lastCounter, on: self)
// - But avoiding retain-cycle
// - Take action whenever lastCounter value changes: label update or query
let vcDetail = Tab2ViewController(detailTapped: state.$counter.eraseToAnyPublisher())
let vcTab = UITabBarController()
vcTab.viewControllers = [vcMain, vcDetail]
vcTab.tabBar.backgroundColor = .systemMint
vcTab.selectedIndex = 0
PlaygroundPage.current.liveView = vcTab
// MARK: -
class Tab1ViewController: UIViewController {
let state: State
var subscriptions: [AnyCancellable] = []
init(state: State) {
self.state = state
super.init(nibName: nil, bundle: nil)
}
internal lazy var button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.darkText, for: .normal)
button.setTitle("Detail", for: .normal)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
tabBarItem = .init(UITabBarItem(tabBarSystemItem: .search, tag: 0))
view.addSubview(button)
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 80),
button.heightAnchor.constraint(equalToConstant: 30),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
// MARK: -
@objc func didTapButton() {
state.counter += 1 // 1. Send out event as sequence number
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class Tab2ViewController: UIViewController {
let detailTapped: AnyPublisher<Int, Never>
var subscriptions: [AnyCancellable] = []
var lastCounter: Int = 0 {
didSet {
button.setTitle("Show detail \(lastCounter)", for: .normal) // 3. Handle event since its a new event
print("counter: \(lastCounter)")
}
}
init(detailTapped: AnyPublisher<Int, Never>) {
self.detailTapped = detailTapped
super.init(nibName: nil, bundle: nil)
}
internal lazy var button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.darkText, for: .normal)
button.setTitle("Detail", for: .normal)
return button
}()
override func viewDidLoad() {
detailTapped.sink { [weak self] counter in
self?.lastCounter = counter
}.store(in: &subscriptions) // 2. Subscribe and update local `lastCounter`
tabBarItem = .init(UITabBarItem(tabBarSystemItem: .search, tag: 0))
view.addSubview(button)
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 80),
button.heightAnchor.constraint(equalToConstant: 30),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class SampleViewController: UIViewController {
internal lazy var button: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitleColor(.darkText, for: .normal)
button.setTitle("Detail", for: .normal)
button.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
return button
}()
init() {
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
tabBarItem = .init(UITabBarItem(tabBarSystemItem: .search, tag: 0))
view.addSubview(button)
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: 80),
button.heightAnchor.constraint(equalToConstant: 30),
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
@objc func didTapButton() {
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment