[Framework] Feed list view in the Feed framework (#9)

This PR contains the work done to provide the existing `FeedViewController` view controller with real life data by integrating the `iTunesService` service into its view model. In addition, the list item cell has been design has been updated, and re-implemented using the `SwiftUI` framework.

Reviewed-on: rock-n-code/app-reviews#9
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit was merged in pull request #9.
This commit is contained in:
2024-03-19 08:31:13 +00:00
committed by javier
parent 26c2c0c581
commit 09df006ab9
16 changed files with 664 additions and 166 deletions
@@ -0,0 +1,100 @@
//
// HostingConfiguration.swift
// ReviewsUIKit
//
// Created by Javier Cicchelli on 18/03/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import SwiftUI
public struct HostingConfiguration<Content: View>: UIContentConfiguration {
// MARK: Type aliases
public typealias ContentClosure = () -> Content
// MARK: Constants
fileprivate let hostingController: UIHostingController<Content>
// MARK: Initialisers
public init(@ViewBuilder content: ContentClosure) {
hostingController = UIHostingController(rootView: content())
}
// MARK: Functions
public func makeContentView() -> UIView & UIContentView {
ContentView<Content>(self)
}
public func updated(
for state: UIConfigurationState
) -> HostingConfiguration<Content> {
self
}
}
// MARK: - Classes
class ContentView<Content: View>: UIView, UIContentView {
// MARK: Properties
var configuration: UIContentConfiguration {
didSet {
configure(configuration)
}
}
// MARK: Initialisers
init(_ configuration: UIContentConfiguration) {
self.configuration = configuration
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
// This view shouldn't be initialized this way so we crash
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Helpers
private extension ContentView {
// MARK: Functions
func configure(_ configuration: UIContentConfiguration) {
guard
let configuration = configuration as? HostingConfiguration<Content>,
let parent = findNextViewController()
else {
return
}
let hostingController = configuration.hostingController
guard
let swiftUICellView = hostingController.view,
subviews.isEmpty
else {
hostingController.view.invalidateIntrinsicContentSize()
return
}
hostingController.view.backgroundColor = .clear
parent.addChild(hostingController)
addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
leadingAnchor.constraint(equalTo: swiftUICellView.leadingAnchor),
trailingAnchor.constraint(equalTo: swiftUICellView.trailingAnchor),
topAnchor.constraint(equalTo: swiftUICellView.topAnchor),
bottomAnchor.constraint(equalTo: swiftUICellView.bottomAnchor)
])
hostingController.didMove(toParent: parent)
}
}
@@ -0,0 +1,24 @@
//
// UIView+Functions.swift
// ReviewsUIKit
//
// Created by Javier Cicchelli on 19/03/2024.
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
//
import UIKit
extension UIView {
// MARK: Functions
func findNextViewController() -> UIViewController? {
if let nextResponder = next as? UIViewController {
return nextResponder
} else if let nextResponder = next as? UIView {
return nextResponder.findNextViewController()
}
return nil
}
}
@@ -0,0 +1,18 @@
//
// View+Modifiers.swift
// ReviewsUIKit
//
// Created by Javier Cicchelli on 19/03/2024.
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
//
import SwiftUI
extension View {
// MARK: Functions
public func fullWidth(alignment: Alignment = .leading) -> some View {
modifier(FullWidthModifier(alignment: alignment))
}
}
@@ -0,0 +1,43 @@
//
// FullWidthModifier.swift
// ReviewsUIKit
//
// Created by Javier Cicchelli on 19/03/2024.
// Copyright © 2024 Röck+Cöde VoF. All rights reserved.
//
import SwiftUI
struct FullWidthModifier: ViewModifier {
// MARK: Constants
private let alignment: Alignment
// MARK: Initialisers
init(alignment: Alignment) {
self.alignment = alignment
}
// MARK: Functions
func body(content: Content) -> some View {
content
.frame(
maxWidth: .infinity,
alignment: alignment
)
}
}
// MARK: - Previews
#Preview {
Group {
Text("Hello, world!")
.modifier(FullWidthModifier(alignment: .leading))
Text("Hello, world!")
.modifier(FullWidthModifier(alignment: .center))
Text("Hello, world!")
.modifier(FullWidthModifier(alignment: .trailing))
}
.padding(.horizontal)
}