Skip to content

Commit 2722e18

Browse files
committed
Redesign NowPlaying with dynamic blur background, SF Symbol controls, and marquee title
1 parent e36c6c7 commit 2722e18

5 files changed

Lines changed: 376 additions & 340 deletions

File tree

SwiftRadio.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
AA0000011ACC9DEE005B7C26 /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000001ACC9DEE005B7C26 /* NetworkService.swift */; };
2626
AA000002291F376D0058C82A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0000001ACC9DEE005B7C26 /* NetworkService.swift */; };
2727
AA0000101ACC9DEE005B7C26 /* LNPopupController in Frameworks */ = {isa = PBXBuildFile; productRef = AA0000111ACC9DEE005B7C26 /* LNPopupController */; };
28+
CA900001200000000000000A /* MarqueeLabel in Frameworks */ = {isa = PBXBuildFile; productRef = CA900002200000000000000A /* MarqueeLabel */; };
2829
AA0000121ACC9DEE005B7C26 /* LNPopupController in Frameworks */ = {isa = PBXBuildFile; productRef = AA0000131ACC9DEE005B7C26 /* LNPopupController */; };
2930
CA142F672D3D8E2F0071A388 /* NSLayoutConstraint+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA142F662D3D8E280071A388 /* NSLayoutConstraint+with.swift */; };
3031
CA142F682D3D8E2F0071A388 /* NSLayoutConstraint+with.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA142F662D3D8E280071A388 /* NSLayoutConstraint+with.swift */; };
@@ -187,6 +188,7 @@
187188
CE37D7B4290F47E000B0933B /* Spring in Frameworks */,
188189
CE6036212A48C51A00E15E15 /* NVActivityIndicatorView in Frameworks */,
189190
AA0000101ACC9DEE005B7C26 /* LNPopupController in Frameworks */,
191+
CA900001200000000000000A /* MarqueeLabel in Frameworks */,
190192
);
191193
runOnlyForDeploymentPostprocessing = 0;
192194
};
@@ -430,6 +432,7 @@
430432
CE6036202A48C51A00E15E15 /* NVActivityIndicatorView */,
431433
CE6036222A48C51A00E15E15 /* NVActivityIndicatorViewExtended */,
432434
AA0000111ACC9DEE005B7C26 /* LNPopupController */,
435+
CA900002200000000000000A /* MarqueeLabel */,
433436
);
434437
productName = RadioPro;
435438
productReference = 9409E1161ABF6FEA00312E2B /* SwiftRadio.app */;
@@ -500,6 +503,7 @@
500503
CE37D7B5290F4A9700B0933B /* XCRemoteSwiftPackageReference "FRadioPlayer" */,
501504
CE60361F2A48C51A00E15E15 /* XCRemoteSwiftPackageReference "NVActivityIndicatorView" */,
502505
AA0000141ACC9DEE005B7C26 /* XCRemoteSwiftPackageReference "LNPopupController" */,
506+
CA900003200000000000000A /* XCRemoteSwiftPackageReference "MarqueeLabel" */,
503507
);
504508
productRefGroup = 9409E1171ABF6FEA00312E2B /* Products */;
505509
projectDirPath = "";
@@ -1022,6 +1026,14 @@
10221026
kind = branch;
10231027
};
10241028
};
1029+
CA900003200000000000000A /* XCRemoteSwiftPackageReference "MarqueeLabel" */ = {
1030+
isa = XCRemoteSwiftPackageReference;
1031+
repositoryURL = "https://github.com/cbpowell/MarqueeLabel.git";
1032+
requirement = {
1033+
kind = upToNextMajorVersion;
1034+
minimumVersion = 4.0.0;
1035+
};
1036+
};
10251037
/* End XCRemoteSwiftPackageReference section */
10261038

10271039
/* Begin XCSwiftPackageProductDependency section */
@@ -1075,6 +1087,11 @@
10751087
package = CE6A3E2F291F376D0058C82A /* XCRemoteSwiftPackageReference "FRadioPlayer" */;
10761088
productName = FRadioPlayer;
10771089
};
1090+
CA900002200000000000000A /* MarqueeLabel */ = {
1091+
isa = XCSwiftPackageProductDependency;
1092+
package = CA900003200000000000000A /* XCRemoteSwiftPackageReference "MarqueeLabel" */;
1093+
productName = MarqueeLabel;
1094+
};
10781095
/* End XCSwiftPackageProductDependency section */
10791096
};
10801097
rootObject = 9409E10E1ABF6FE900312E2B /* Project object */;

SwiftRadio.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 10 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SwiftRadio/ViewControllers/NowPlayingViewController.swift

Lines changed: 77 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,27 @@ class NowPlayingViewController: UIViewController {
2525

2626
// MARK: - UI
2727
private let backgroundImageView: UIImageView = {
28-
let image = UIImage(named: "background")
29-
let imageView = UIImageView(image: image)
28+
let imageView = UIImageView()
3029
imageView.contentMode = .scaleAspectFill
30+
imageView.clipsToBounds = true
3131
imageView.translatesAutoresizingMaskIntoConstraints = false
3232
return imageView
3333
}()
3434

35+
private let backgroundBlurView: UIVisualEffectView = {
36+
let blur = UIBlurEffect(style: .systemThickMaterialDark)
37+
let view = UIVisualEffectView(effect: blur)
38+
view.translatesAutoresizingMaskIntoConstraints = false
39+
return view
40+
}()
41+
42+
private let backgroundDimView: UIView = {
43+
let view = UIView()
44+
view.backgroundColor = UIColor.black.withAlphaComponent(0.3)
45+
view.translatesAutoresizingMaskIntoConstraints = false
46+
return view
47+
}()
48+
3549
private let albumArtworkView = AlbumArtworkView()
3650
private let controlsView = ControlsView()
3751
private var topConstraint: NSLayoutConstraint?
@@ -65,19 +79,21 @@ class NowPlayingViewController: UIViewController {
6579

6680
override func viewDidLayoutSubviews() {
6781
super.viewDidLayoutSubviews()
68-
topConstraint?.constant = navigationController != nil ? 8 : 40
82+
topConstraint?.constant = navigationController != nil ? 16 : 48
6983
}
7084

7185
private func isPlayingDidChange(_ isPlaying: Bool) {
7286
controlsView.setPlaying(isPlaying)
73-
controlsView.setStop(isPlaying)
87+
albumArtworkView.setPlaying(isPlaying)
7488
updatePopupBarPlayPauseButton(isPlaying: isPlaying)
7589
}
7690

7791
func stationDidChange() {
7892
albumArtworkView.setImage(nil)
93+
updateBackground(with: nil)
7994
manager.currentStation?.getImage { [weak self] image in
8095
self?.albumArtworkView.setImage(image)
96+
self?.updateBackground(with: image)
8197
self?.updatePopupBarImage(image)
8298
}
8399

@@ -86,78 +102,63 @@ class NowPlayingViewController: UIViewController {
86102
controlsView.setLive(player.duration == 0)
87103
}
88104

89-
func updateLabels(with statusMessage: String? = nil, animate: Bool = true) {
90-
guard let statusMessage = statusMessage else {
91-
controlsView.updateLabels(with: .track(song: manager.currentStation?.trackName,
92-
artist: manager.currentStation?.artistName))
93-
return
94-
}
95-
96-
controlsView.updateLabels(with: .status(message: statusMessage, name: manager.currentStation?.name))
105+
func updateLabels() {
106+
controlsView.updateNowPlaying(
107+
song: player.currentMetadata?.trackName,
108+
artist: player.currentMetadata?.artistName,
109+
stationName: manager.currentStation?.name,
110+
stationDesc: manager.currentStation?.desc
111+
)
97112
}
98113

99-
func playbackStateDidChange(_ playbackState: FRadioPlayer.PlaybackState, animate: Bool) {
100-
101-
let message: String?
102-
103-
switch playbackState {
104-
case .paused:
105-
message = "Paused..."
106-
case .playing:
107-
message = nil
108-
case .stopped:
109-
message = "Stopped..."
110-
}
111-
112-
updateLabels(with: message, animate: animate)
114+
func playbackStateDidChange(_ playbackState: FRadioPlayer.PlaybackState) {
113115
isPlayingDidChange(player.isPlaying)
114116
}
115117

116-
func playerStateDidChange(_ state: FRadioPlayer.State, animate: Bool) {
117-
118-
let message: String?
119-
118+
func playerStateDidChange(_ state: FRadioPlayer.State) {
120119
switch state {
121-
case .loading:
122-
message = "Loading ..."
123-
case .urlNotSet:
124-
message = "URL not valide"
125120
case .readyToPlay, .loadingFinished:
126-
playbackStateDidChange(player.playbackState, animate: animate)
127-
return
128-
case .error:
129-
message = "Error Playing"
121+
playbackStateDidChange(player.playbackState)
122+
default:
123+
break
130124
}
131-
132-
updateLabels(with: message, animate: animate)
133125
}
134126

135127
// MARK: - Setup Methods
136128

137129
private func setupViews() {
130+
// Dynamic blurred background
138131
view.addSubview(backgroundImageView)
132+
view.addSubview(backgroundBlurView)
133+
view.addSubview(backgroundDimView)
139134

140135
NSLayoutConstraint.activate([
141136
backgroundImageView.topAnchor.constraint(equalTo: view.topAnchor),
142-
backgroundImageView.rightAnchor.constraint(equalTo: view.rightAnchor),
137+
backgroundImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
138+
backgroundImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
143139
backgroundImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
144-
backgroundImageView.leftAnchor.constraint(equalTo: view.leftAnchor)
140+
141+
backgroundBlurView.topAnchor.constraint(equalTo: view.topAnchor),
142+
backgroundBlurView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
143+
backgroundBlurView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
144+
backgroundBlurView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
145+
146+
backgroundDimView.topAnchor.constraint(equalTo: view.topAnchor),
147+
backgroundDimView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
148+
backgroundDimView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
149+
backgroundDimView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
145150
])
146151

147152
let mainStackView = UIStackView(arrangedSubviews: [albumArtworkView, controlsView])
148153
mainStackView.axis = .vertical
149-
mainStackView.spacing = 20
150-
mainStackView.distribution = .fillEqually
154+
mainStackView.spacing = 24
155+
mainStackView.distribution = .fill
151156
mainStackView.translatesAutoresizingMaskIntoConstraints = false
152157

153158
controlsView.playingAction = { [unowned self] in
154159
player.togglePlaying()
155160
}
156161

157-
controlsView.stopAction = { [unowned self] in
158-
player.stop()
159-
}
160-
161162
controlsView.nextAction = { [unowned self] in
162163
manager.setNext()
163164
}
@@ -166,10 +167,6 @@ class NowPlayingViewController: UIViewController {
166167
manager.setPrevious()
167168
}
168169

169-
controlsView.logoAction = { [unowned self] in
170-
delegate?.didTapCompanyButton(self)
171-
}
172-
173170
controlsView.moreAction = { [unowned self] in
174171
handleMoreMenu()
175172
}
@@ -182,34 +179,50 @@ class NowPlayingViewController: UIViewController {
182179

183180
topConstraint = mainStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
184181

182+
let artworkHeight = albumArtworkView.heightAnchor.constraint(equalTo: mainStackView.heightAnchor, multiplier: 0.55)
183+
artworkHeight.priority = .defaultHigh
184+
185185
NSLayoutConstraint.activate([
186186
topConstraint!,
187-
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20),
188-
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20),
189-
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20)
187+
mainStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 24),
188+
mainStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -24),
189+
mainStackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
190+
artworkHeight,
190191
])
191192
}
192193

194+
// MARK: - Dynamic Background
195+
196+
private func updateBackground(with image: UIImage?) {
197+
UIView.transition(
198+
with: backgroundImageView,
199+
duration: 0.5,
200+
options: .transitionCrossDissolve
201+
) {
202+
self.backgroundImageView.image = image
203+
}
204+
}
205+
193206
func updateTrackArtwork() {
194-
getTrackArtwork { [weak self] image, isAnimated in
207+
getTrackArtwork { [weak self] image in
195208
DispatchQueue.main.async {
196-
self?.albumArtworkView.setImage(image)
197-
if isAnimated { self?.albumArtworkView.animate() }
209+
self?.albumArtworkView.setImage(image, animated: true)
210+
self?.updateBackground(with: image)
198211
self?.updatePopupBarImage(image)
199212
}
200213
}
201214
}
202215

203-
private func getTrackArtwork(completion: @escaping (UIImage?, Bool) -> Void) {
216+
private func getTrackArtwork(completion: @escaping (UIImage?) -> Void) {
204217
guard let artworkURL = player.currentArtworkURL else {
205218
manager.currentStation?.getImage { image in
206-
completion(image, false)
219+
completion(image)
207220
}
208221
return
209222
}
210223

211224
UIImage.image(from: artworkURL) { image in
212-
completion(image, true)
225+
completion(image)
213226
}
214227
}
215228

@@ -273,11 +286,11 @@ class NowPlayingViewController: UIViewController {
273286
extension NowPlayingViewController: FRadioPlayerObserver {
274287

275288
func radioPlayer(_ player: FRadioPlayer, playerStateDidChange state: FRadioPlayer.State) {
276-
playerStateDidChange(state, animate: true)
289+
playerStateDidChange(state)
277290
}
278291

279292
func radioPlayer(_ player: FRadioPlayer, playbackStateDidChange state: FRadioPlayer.PlaybackState) {
280-
playbackStateDidChange(state, animate: true)
293+
playbackStateDidChange(state)
281294
}
282295

283296
func radioPlayer(_ player: FRadioPlayer, metadataDidChange metadata: FRadioPlayer.Metadata?) {
@@ -317,7 +330,7 @@ extension NowPlayingViewController: BottomSheetViewControllerDelegate {
317330

318331
func bottomSheet(_ controller: BottomSheetViewController, didSelect option: BottomSheetViewController.Option) {
319332
if case .share = option {
320-
getTrackArtwork { [weak self] image, _ in
333+
getTrackArtwork { [weak self] image in
321334
guard let self = self else { return }
322335
self.delegate?.didSelectBottomSheetOption(.share(image), from: self)
323336
}

0 commit comments

Comments
 (0)