@stream-io/video-react-native-sdk 1.29.4 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/commonjs/components/Call/CallContent/CallContent.js +13 -7
- package/dist/commonjs/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js +50 -14
- package/dist/commonjs/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js +27 -0
- package/dist/commonjs/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js +12 -9
- package/dist/commonjs/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js +19 -4
- package/dist/commonjs/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/commonjs/utils/hooks/index.js +0 -11
- package/dist/commonjs/utils/hooks/index.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallContent/CallContent.js +10 -4
- package/dist/module/components/Call/CallContent/CallContent.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js +52 -16
- package/dist/module/components/Call/CallContent/RTCViewPipIOS.js.map +1 -1
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js +27 -0
- package/dist/module/components/Call/CallContent/RTCViewPipNative.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js +19 -10
- package/dist/module/components/Call/CallLayout/CallParticipantsGrid.js.map +1 -1
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js +15 -12
- package/dist/module/components/Call/CallLayout/CallParticipantsSpotlight.js.map +1 -1
- package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js +20 -5
- package/dist/module/components/Call/CallParticipantsList/CallParticipantsList.js.map +1 -1
- package/dist/module/utils/hooks/index.js +0 -1
- package/dist/module/utils/hooks/index.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/CallContent/CallContent.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipIOS.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts +18 -0
- package/dist/typescript/components/Call/CallContent/RTCViewPipNative.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsGrid.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallLayout/CallParticipantsSpotlight.d.ts.map +1 -1
- package/dist/typescript/components/Call/CallParticipantsList/CallParticipantsList.d.ts.map +1 -1
- package/dist/typescript/utils/hooks/index.d.ts +0 -1
- package/dist/typescript/utils/hooks/index.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/ios/PictureInPicture/PictureInPictureAvatarView.swift +273 -0
- package/ios/PictureInPicture/PictureInPictureConnectionQualityIndicator.swift +162 -0
- package/ios/PictureInPicture/PictureInPictureContent.swift +173 -0
- package/ios/PictureInPicture/PictureInPictureContentState.swift +123 -0
- package/ios/PictureInPicture/PictureInPictureDelegateProxy.swift +89 -0
- package/ios/PictureInPicture/PictureInPictureEnforcedStopAdapter.swift +166 -0
- package/ios/PictureInPicture/PictureInPictureLogger.swift +16 -0
- package/ios/PictureInPicture/PictureInPictureParticipantOverlayView.swift +217 -0
- package/ios/PictureInPicture/PictureInPictureReconnectionView.swift +193 -0
- package/ios/PictureInPicture/StreamAVPictureInPictureVideoCallViewController.swift +125 -7
- package/ios/PictureInPicture/StreamPictureInPictureController.swift +237 -63
- package/ios/PictureInPicture/StreamPictureInPictureControllerProtocol.swift +30 -0
- package/ios/PictureInPicture/StreamPictureInPictureVideoRenderer.swift +384 -12
- package/ios/RTCViewPip.swift +187 -21
- package/ios/RTCViewPipManager.mm +9 -0
- package/ios/RTCViewPipManager.swift +3 -3
- package/package.json +3 -3
- package/src/components/Call/CallContent/CallContent.tsx +16 -8
- package/src/components/Call/CallContent/RTCViewPipIOS.tsx +81 -15
- package/src/components/Call/CallContent/RTCViewPipNative.tsx +36 -0
- package/src/components/Call/CallLayout/CallParticipantsGrid.tsx +28 -14
- package/src/components/Call/CallLayout/CallParticipantsSpotlight.tsx +19 -10
- package/src/components/Call/CallParticipantsList/CallParticipantsList.tsx +20 -5
- package/src/utils/hooks/index.ts +0 -1
- package/src/version.ts +1 -1
- package/dist/commonjs/utils/hooks/useDebouncedValue.js +0 -24
- package/dist/commonjs/utils/hooks/useDebouncedValue.js.map +0 -1
- package/dist/module/utils/hooks/useDebouncedValue.js +0 -19
- package/dist/module/utils/hooks/useDebouncedValue.js.map +0 -1
- package/dist/typescript/utils/hooks/useDebouncedValue.d.ts +0 -8
- package/dist/typescript/utils/hooks/useDebouncedValue.d.ts.map +0 -1
- package/src/utils/hooks/useDebouncedValue.ts +0 -21
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright © 2024 Stream.io Inc. All rights reserved.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import UIKit
|
|
6
|
+
|
|
7
|
+
/// A view that displays a reconnection indicator when the call connection is being recovered.
|
|
8
|
+
/// Shows three pulsing dots with a "Reconnecting" message, matching upstream CallingIndicator style.
|
|
9
|
+
final class PictureInPictureReconnectionView: UIView {
|
|
10
|
+
|
|
11
|
+
// MARK: - Properties
|
|
12
|
+
|
|
13
|
+
/// Whether the view should be visible (when reconnecting)
|
|
14
|
+
var isReconnecting: Bool = false {
|
|
15
|
+
didSet {
|
|
16
|
+
updateVisibility()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// MARK: - Private Properties
|
|
21
|
+
|
|
22
|
+
private let containerView: UIView = {
|
|
23
|
+
let view = UIView()
|
|
24
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
|
25
|
+
view.backgroundColor = UIColor(red: 0.12, green: 0.13, blue: 0.15, alpha: 0.85)
|
|
26
|
+
return view
|
|
27
|
+
}()
|
|
28
|
+
|
|
29
|
+
private let contentStackView: UIStackView = {
|
|
30
|
+
let stack = UIStackView()
|
|
31
|
+
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
32
|
+
stack.axis = .vertical
|
|
33
|
+
stack.alignment = .center
|
|
34
|
+
stack.spacing = 8
|
|
35
|
+
return stack
|
|
36
|
+
}()
|
|
37
|
+
|
|
38
|
+
private let messageLabel: UILabel = {
|
|
39
|
+
let label = UILabel()
|
|
40
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
41
|
+
label.text = "Reconnecting"
|
|
42
|
+
label.textColor = .white
|
|
43
|
+
label.textAlignment = .center
|
|
44
|
+
label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
|
|
45
|
+
label.accessibilityIdentifier = "reconnectingMessage"
|
|
46
|
+
return label
|
|
47
|
+
}()
|
|
48
|
+
|
|
49
|
+
/// Three dots indicator matching upstream CallingIndicator style
|
|
50
|
+
private let dotsStackView: UIStackView = {
|
|
51
|
+
let stack = UIStackView()
|
|
52
|
+
stack.translatesAutoresizingMaskIntoConstraints = false
|
|
53
|
+
stack.axis = .horizontal
|
|
54
|
+
stack.alignment = .center
|
|
55
|
+
stack.spacing = 2 // Matches upstream
|
|
56
|
+
stack.accessibilityIdentifier = "callingIndicator"
|
|
57
|
+
return stack
|
|
58
|
+
}()
|
|
59
|
+
|
|
60
|
+
private let dotSize: CGFloat = 4 // Matches upstream
|
|
61
|
+
private var dots: [UIView] = []
|
|
62
|
+
|
|
63
|
+
// MARK: - Lifecycle
|
|
64
|
+
|
|
65
|
+
override init(frame: CGRect) {
|
|
66
|
+
super.init(frame: frame)
|
|
67
|
+
setUp()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
required init?(coder: NSCoder) {
|
|
71
|
+
fatalError("init(coder:) has not been implemented")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
deinit {
|
|
75
|
+
stopAnimation()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - Private Helpers
|
|
79
|
+
|
|
80
|
+
private func createDot() -> UIView {
|
|
81
|
+
let dot = UIView()
|
|
82
|
+
dot.translatesAutoresizingMaskIntoConstraints = false
|
|
83
|
+
dot.backgroundColor = .white
|
|
84
|
+
dot.layer.cornerRadius = dotSize / 2
|
|
85
|
+
dot.alpha = 0 // Start invisible (matches upstream)
|
|
86
|
+
NSLayoutConstraint.activate([
|
|
87
|
+
dot.widthAnchor.constraint(equalToConstant: dotSize),
|
|
88
|
+
dot.heightAnchor.constraint(equalToConstant: dotSize)
|
|
89
|
+
])
|
|
90
|
+
return dot
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private func setUp() {
|
|
94
|
+
addSubview(containerView)
|
|
95
|
+
containerView.addSubview(contentStackView)
|
|
96
|
+
|
|
97
|
+
// Order matches upstream: text first, then dots indicator
|
|
98
|
+
contentStackView.addArrangedSubview(messageLabel)
|
|
99
|
+
contentStackView.addArrangedSubview(dotsStackView)
|
|
100
|
+
|
|
101
|
+
// Add three dots (matches upstream)
|
|
102
|
+
for _ in 0..<3 {
|
|
103
|
+
let dot = createDot()
|
|
104
|
+
dots.append(dot)
|
|
105
|
+
dotsStackView.addArrangedSubview(dot)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
NSLayoutConstraint.activate([
|
|
109
|
+
containerView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
110
|
+
containerView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
111
|
+
containerView.topAnchor.constraint(equalTo: topAnchor),
|
|
112
|
+
containerView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
113
|
+
|
|
114
|
+
contentStackView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
|
|
115
|
+
contentStackView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
|
|
116
|
+
contentStackView.leadingAnchor.constraint(greaterThanOrEqualTo: containerView.leadingAnchor, constant: 16),
|
|
117
|
+
contentStackView.trailingAnchor.constraint(lessThanOrEqualTo: containerView.trailingAnchor, constant: -16)
|
|
118
|
+
])
|
|
119
|
+
|
|
120
|
+
// Initially hidden
|
|
121
|
+
updateVisibility()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
override func didMoveToWindow() {
|
|
125
|
+
super.didMoveToWindow()
|
|
126
|
+
// Restart animation when view is added to window (animations are removed when view leaves window)
|
|
127
|
+
if window != nil && isReconnecting && !isHidden {
|
|
128
|
+
startAnimation()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private func updateVisibility() {
|
|
133
|
+
isHidden = !isReconnecting
|
|
134
|
+
|
|
135
|
+
if isReconnecting {
|
|
136
|
+
startAnimation()
|
|
137
|
+
} else {
|
|
138
|
+
stopAnimation()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// MARK: - Animation (matches upstream CallingIndicator)
|
|
143
|
+
|
|
144
|
+
/// Starts the pulsing animation matching upstream exactly:
|
|
145
|
+
/// - All dots animate from alpha 0 → 1
|
|
146
|
+
/// - Same 0.2s delay for all dots
|
|
147
|
+
/// - 1 second duration
|
|
148
|
+
/// - Different easing: easeOut, easeInOut, easeIn
|
|
149
|
+
/// - Repeat forever with autoreverse
|
|
150
|
+
private func startAnimation() {
|
|
151
|
+
// Only animate if we're in a window
|
|
152
|
+
guard window != nil else {
|
|
153
|
+
PictureInPictureLogger.log("ReconnectionView: startAnimation called but not in window yet")
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
PictureInPictureLogger.log("ReconnectionView: starting dot animation with CABasicAnimation")
|
|
158
|
+
|
|
159
|
+
// Stop any existing animations first
|
|
160
|
+
stopAnimation()
|
|
161
|
+
|
|
162
|
+
// Use CABasicAnimation for better compatibility with PiP
|
|
163
|
+
// Matches upstream: easeOut, easeInOut, easeIn timing functions
|
|
164
|
+
let timingFunctions: [CAMediaTimingFunction] = [
|
|
165
|
+
CAMediaTimingFunction(name: .easeOut),
|
|
166
|
+
CAMediaTimingFunction(name: .easeInEaseOut),
|
|
167
|
+
CAMediaTimingFunction(name: .easeIn)
|
|
168
|
+
]
|
|
169
|
+
|
|
170
|
+
for (index, dot) in dots.enumerated() {
|
|
171
|
+
let animation = CABasicAnimation(keyPath: "opacity")
|
|
172
|
+
animation.fromValue = 0.0
|
|
173
|
+
animation.toValue = 1.0
|
|
174
|
+
animation.duration = 1.0
|
|
175
|
+
animation.beginTime = CACurrentMediaTime() + 0.2 // 0.2s delay
|
|
176
|
+
animation.timingFunction = timingFunctions[index]
|
|
177
|
+
animation.autoreverses = true
|
|
178
|
+
animation.repeatCount = .infinity
|
|
179
|
+
animation.fillMode = .forwards
|
|
180
|
+
animation.isRemovedOnCompletion = false
|
|
181
|
+
|
|
182
|
+
dot.layer.add(animation, forKey: "pulseAnimation")
|
|
183
|
+
dot.alpha = 0 // Set initial state
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private func stopAnimation() {
|
|
188
|
+
dots.forEach { dot in
|
|
189
|
+
dot.layer.removeAllAnimations()
|
|
190
|
+
dot.alpha = 0
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -7,13 +7,13 @@ import Foundation
|
|
|
7
7
|
|
|
8
8
|
/// Describes an object that can be used to present picture-in-picture content.
|
|
9
9
|
protocol StreamAVPictureInPictureViewControlling: AnyObject {
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
/// The closure to call whenever the picture-in-picture window size changes.
|
|
12
12
|
var onSizeUpdate: ((CGSize) -> Void)? { get set }
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
/// The track that will be rendered on picture-in-picture window.
|
|
15
15
|
var track: RTCVideoTrack? { get set }
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
/// The preferred size for the picture-in-picture window.
|
|
18
18
|
/// - Important: This should **always** be greater to ``CGSize.zero``. If not, iOS throws
|
|
19
19
|
/// a cryptic error with content `PGPegasus code:-1003`
|
|
@@ -24,17 +24,65 @@ protocol StreamAVPictureInPictureViewControlling: AnyObject {
|
|
|
24
24
|
|
|
25
25
|
/// The layer that renders the incoming frames from WebRTC.
|
|
26
26
|
var displayLayer: CALayer { get }
|
|
27
|
+
|
|
28
|
+
// MARK: - Avatar Placeholder Properties
|
|
29
|
+
|
|
30
|
+
/// The participant's name for the avatar placeholder
|
|
31
|
+
var participantName: String? { get set }
|
|
32
|
+
|
|
33
|
+
/// The URL string for the participant's profile image
|
|
34
|
+
var participantImageURL: String? { get set }
|
|
35
|
+
|
|
36
|
+
/// Whether video is enabled - when false, shows avatar placeholder
|
|
37
|
+
var isVideoEnabled: Bool { get set }
|
|
38
|
+
|
|
39
|
+
// MARK: - Reconnection Properties
|
|
40
|
+
|
|
41
|
+
/// Whether the call is reconnecting - when true, shows reconnection view
|
|
42
|
+
var isReconnecting: Bool { get set }
|
|
43
|
+
|
|
44
|
+
// MARK: - Screen Sharing Properties
|
|
45
|
+
|
|
46
|
+
/// Whether screen sharing is active (used for content state tracking)
|
|
47
|
+
var isScreenSharing: Bool { get set }
|
|
48
|
+
|
|
49
|
+
// MARK: - Participant Overlay Properties
|
|
50
|
+
|
|
51
|
+
/// Whether the participant has audio enabled (shown in participant overlay)
|
|
52
|
+
var hasAudio: Bool { get set }
|
|
53
|
+
|
|
54
|
+
/// Whether the video track is paused (shown in participant overlay)
|
|
55
|
+
var isTrackPaused: Bool { get set }
|
|
56
|
+
|
|
57
|
+
/// Whether the participant is pinned (shown in participant overlay)
|
|
58
|
+
var isPinned: Bool { get set }
|
|
59
|
+
|
|
60
|
+
/// Whether the participant is currently speaking (shows border highlight)
|
|
61
|
+
var isSpeaking: Bool { get set }
|
|
62
|
+
|
|
63
|
+
/// The connection quality level (0: unknown, 1: poor, 2: good, 3: excellent)
|
|
64
|
+
var connectionQuality: Int { get set }
|
|
65
|
+
|
|
66
|
+
// MARK: - Content State System
|
|
67
|
+
|
|
68
|
+
/// The content state manager for unified state handling.
|
|
69
|
+
/// When set, the view controller subscribes to content changes automatically.
|
|
70
|
+
var contentState: PictureInPictureContentState? { get set }
|
|
71
|
+
|
|
72
|
+
/// The current content being displayed.
|
|
73
|
+
/// Can be set directly for one-off updates or managed via contentState for reactive updates.
|
|
74
|
+
var content: PictureInPictureContent { get set }
|
|
27
75
|
}
|
|
28
76
|
|
|
29
77
|
@available(iOS 15.0, *)
|
|
30
78
|
final class StreamAVPictureInPictureVideoCallViewController: AVPictureInPictureVideoCallViewController,
|
|
31
79
|
StreamAVPictureInPictureViewControlling {
|
|
32
|
-
|
|
80
|
+
|
|
33
81
|
private let contentView: StreamPictureInPictureVideoRenderer =
|
|
34
82
|
.init(windowSizePolicy: StreamPictureInPictureAdaptiveWindowSizePolicy())
|
|
35
|
-
|
|
83
|
+
|
|
36
84
|
var onSizeUpdate: ((CGSize) -> Void)?
|
|
37
|
-
|
|
85
|
+
|
|
38
86
|
var track: RTCVideoTrack? {
|
|
39
87
|
get { contentView.track }
|
|
40
88
|
set { contentView.track = newValue }
|
|
@@ -46,7 +94,77 @@ final class StreamAVPictureInPictureVideoCallViewController: AVPictureInPictureV
|
|
|
46
94
|
}
|
|
47
95
|
|
|
48
96
|
var displayLayer: CALayer { contentView.displayLayer }
|
|
49
|
-
|
|
97
|
+
|
|
98
|
+
// MARK: - Avatar Placeholder Properties
|
|
99
|
+
|
|
100
|
+
var participantName: String? {
|
|
101
|
+
get { contentView.participantName }
|
|
102
|
+
set { contentView.participantName = newValue }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var participantImageURL: String? {
|
|
106
|
+
get { contentView.participantImageURL }
|
|
107
|
+
set { contentView.participantImageURL = newValue }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
var isVideoEnabled: Bool {
|
|
111
|
+
get { contentView.isVideoEnabled }
|
|
112
|
+
set { contentView.isVideoEnabled = newValue }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// MARK: - Reconnection Properties
|
|
116
|
+
|
|
117
|
+
var isReconnecting: Bool {
|
|
118
|
+
get { contentView.isReconnecting }
|
|
119
|
+
set { contentView.isReconnecting = newValue }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MARK: - Screen Sharing Properties
|
|
123
|
+
|
|
124
|
+
var isScreenSharing: Bool {
|
|
125
|
+
get { contentView.isScreenSharing }
|
|
126
|
+
set { contentView.isScreenSharing = newValue }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// MARK: - Participant Overlay Properties
|
|
130
|
+
|
|
131
|
+
var hasAudio: Bool {
|
|
132
|
+
get { contentView.hasAudio }
|
|
133
|
+
set { contentView.hasAudio = newValue }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
var isTrackPaused: Bool {
|
|
137
|
+
get { contentView.isTrackPaused }
|
|
138
|
+
set { contentView.isTrackPaused = newValue }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
var isPinned: Bool {
|
|
142
|
+
get { contentView.isPinned }
|
|
143
|
+
set { contentView.isPinned = newValue }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
var isSpeaking: Bool {
|
|
147
|
+
get { contentView.isSpeaking }
|
|
148
|
+
set { contentView.isSpeaking = newValue }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
var connectionQuality: Int {
|
|
152
|
+
get { contentView.connectionQuality }
|
|
153
|
+
set { contentView.connectionQuality = newValue }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Content State System
|
|
157
|
+
|
|
158
|
+
var contentState: PictureInPictureContentState? {
|
|
159
|
+
get { contentView.contentState }
|
|
160
|
+
set { contentView.contentState = newValue }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
var content: PictureInPictureContent {
|
|
164
|
+
get { contentView.content }
|
|
165
|
+
set { contentView.content = newValue }
|
|
166
|
+
}
|
|
167
|
+
|
|
50
168
|
// MARK: - Lifecycle
|
|
51
169
|
|
|
52
170
|
@available(*, unavailable)
|