Swiftui Dynamic CloudKIt predicate - dynamic

I am trying to make a dynamic predicate for Cloudkit to return items that match the segmented picker control. so for example
let predicate = NSPredicate(format: "department == %#", "Marketing") the 'marketing' part of the predicate would be a String variable which changes depending on the segmented picker control. I have tried #State #Published but none work. I have Googled Cloudkit dynamic predicates but there is little out there. Any help would be gratefully received.
This is my code
class staffCloudKitViewModel: ObservableObject {
#Published var text: String = ""
#Published var staff: [StaffModel] = []
#Published var predicateString: String = ""
init(){
fetchItems()
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(red: 116/255,
green: 152/255, blue: 192/255, alpha: 1.0)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor:
UIColor.white], for: .selected)
}
func fetchItems() {
//let predicate = NSPredicate(value: true)
let predicate = NSPredicate(format: "department == %#", "Marketing")
let query = CKQuery(recordType: "Staff", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "lastName", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
var returnedItems: [StaffModel] = []
queryOperation.recordMatchedBlock = { (returnedRecordID, returnedResult) in
switch returnedResult {
case .success(let record):
guard let firstName = record["firstName"] as? String else { return }
guard let lastName = record["lastName"] as? String else { return }
guard let department = record["department"] as? String else { return }
guard let country = record["country"] as? String else { return }
guard let mobile = record["phone"] as? String else { return }
guard let job = record["job"] as? String else { return }
let imageAsset = record["headshot"] as? CKAsset
let imageURL = imageAsset?.fileURL
returnedItems.append(StaffModel(firstName: firstName, lastName: lastName,
country: country, department: department, phoneNumber: mobile, job: job,
headshot:
imageURL, record: record))
case .failure(let error):
print("Error recordMatchedBlock: \(error)")
}
}
queryOperation.queryResultBlock = { [weak self] returnedResult in
print("RETURNED RESULT \(returnedResult)")
DispatchQueue.main.async {
self?.staff = returnedItems
}
}
addOperation(opertaion: queryOperation)
}
func addOperation(opertaion: CKDatabaseOperation) {
CKContainer.default().publicCloudDatabase.add(opertaion)
}
}
struct ContentView: View {
#State private var countryselected: Country = .UK
#StateObject private var vm = staffCloudKitViewModel()
var body: some View {
NavigationView {
VStack {
Picker("Select country", selection: $countryselected) {
ForEach(Country.allCases, id: \.self){
Text($0.rawValue)
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()
List {
ForEach(vm.staff, id: \.self) { thepeople in
VStack (alignment: .leading) {

Related

Swift: Can't change class attribute from #objc function

I'm trying to capture ProximitySensor activity on SwiftUI.
So I've created a class ProximityOberver and trying to update the attribute 'state' in the notification:
import SwiftUI
import UIKit
class ProximityObserver {
#State var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
state = device.proximityState
print(state)
}
}
}
struct ContentView: View {
#State var proximityObserver = ProximityObserver()
func activateProximitySensor() {
print("MyView::activateProximitySensor")
if !UIDevice.current.isProximityMonitoringEnabled {
UIDevice.current.isProximityMonitoringEnabled = true
if UIDevice.current.isProximityMonitoringEnabled {
NotificationCenter.default.addObserver(proximityObserver, selector: #selector(proximityObserver.didChange), name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
}
}
func deactivateProximitySensor() {
print("MyView::deactivateProximitySensor")
UIDevice.current.isProximityMonitoringEnabled = false
NotificationCenter.default.removeObserver(proximityObserver, name: UIDevice.proximityStateDidChangeNotification, object: UIDevice.current)
}
var body: some View {
Text(proximityObserver.state ? "true" : "false" )
.animation(.linear(duration: 20).delay(20), value: proximityObserver.state)
.onAppear() {
self.activateProximitySensor()
}
.onDisappear() {
self.deactivateProximitySensor()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But even 'state = device.proximityState' code executed, the following print(state) shows the attribute never changed.
MyView::ProximityObserver.didChange
true
false
Can someone explain why this happens, and how to fix this?
Thank you for the comment.
I could fix this as suggested.
class ProximityObserver: ObservableObject {
#Published var state = false;
#objc func didChange(notification: NSNotification) {
print("MyView::ProximityObserver.didChange")
if let device = notification.object as? UIDevice {
print(device.proximityState)
self.state = device.proximityState
print(state, device.proximityState)
}
}
}
struct ContentView: View {
#ObservedObject var proximityObserver = ProximityObserver()
...

Choose file in external storage and get path

i know this question has been asked several times but everything i can find is only in Java and not very relevant to me...
I'm trying to select a file when i click on a button in my app (images or videos like : /storage/emulated/0/Download/giphy.gif ) and the when the picture or video is selected, i need the path to go inside an edittext.
I have found that code in kotlin for path :
class URIPathHelper {
fun getPath(context: Context, uri: Uri): String? {
val isKitKatorAbove = true
// DocumentProvider
if (isKitKatorAbove && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
} else if (isDownloadsDocument(uri)) {
val id = DocumentsContract.getDocumentId(uri)
val contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), java.lang.Long.valueOf(id))
return getDataColumn(context, contentUri, null, null)
} else if (isMediaDocument(uri)) {
val docId = DocumentsContract.getDocumentId(uri)
val split = docId.split(":".toRegex()).toTypedArray()
val type = split[0]
var contentUri: Uri? = null
if ("image" == type) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
} else if ("video" == type) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
} else if ("audio" == type) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
}
val selection = "_id=?"
val selectionArgs = arrayOf(split[1])
return getDataColumn(context, contentUri, selection, selectionArgs)
}
} else if ("content".equals(uri.scheme, ignoreCase = true)) {
return getDataColumn(context, uri, null, null)
} else if ("file".equals(uri.scheme, ignoreCase = true)) {
return uri.path
}
return null
}
fun getDataColumn(context: Context, uri: Uri?, selection: String?, selectionArgs: Array<String>?): String? {
var cursor: Cursor? = null
val column = "_data"
val projection = arrayOf(column)
try {
cursor = uri?.let { context.getContentResolver().query(it, projection, selection, selectionArgs,null) }
if (cursor != null && cursor.moveToFirst()) {
val column_index: Int = cursor.getColumnIndexOrThrow(column)
return cursor.getString(column_index)
}
} finally {
if (cursor != null) cursor.close()
}
return null
}
fun isExternalStorageDocument(uri: Uri): Boolean {
return "com.android.externalstorage.documents" == uri.authority
}
fun isDownloadsDocument(uri: Uri): Boolean {
return "com.android.providers.downloads.documents" == uri.authority
}
fun isMediaDocument(uri: Uri): Boolean {
return "com.android.providers.media.documents" == uri.authority
}
}
And this is what i'm trying :
binding.redloader.setOnClickListener {
val uriPathHelper = URIPathHelper()
val filePath: String? = uriPathHelper.getPath(this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
val uri: Uri = Uri.parse(filePath)
val intent = Intent(Intent.ACTION_VIEW)
intent.type = "image/*"
intent.putExtra(Intent.ACTION_VIEW, uri)
startActivity(Intent.createChooser(intent, "Open file"))
binding.redgif.setText(filePath)
}
When I click on the button it automatically chooses the first picture in the storage, and then it opens google picture but I cannot do anything except watch the pictures...
I'm sorry I know they are some stupid things in my code, I think I have something to do with intent but all I see on the internet is calling private void and it is not working for me...
I'm a complete beginner and I really hope someone can help me...
Update with what i'm trying, i think i'm close :
binding.redloader.setOnClickListener {
val uriPathHelper = URIPathHelper()
intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent = Intent.createChooser(intent, "Choose a file")
val resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
if (data != null) {
val fileUri: Uri? = data.data
Log.i(LOG_TAG, fileUri.toString())
var fileURIPathHelper: String? = null
try {
fileURIPathHelper = uriPathHelper.getPath(this, EXTERNAL_CONTENT_URI)
} catch (e: Exception) {
Log.e(LOG_TAG, "Error: " + e)
Toast.makeText(this, "Error: " + e, Toast.LENGTH_SHORT).show()
}
this.binding.redgif.setText(fileURIPathHelper)
}
}
}
resultLauncher.launch(intent)
}
I solved it ! :)
val resultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
if (data != null) {
val fileUri: Uri? = data.data
Log.i(PackageManagerCompat.LOG_TAG, fileUri.toString())
val contentUri: String?
try {
contentUri = uriPathHelper.getPath(this, fileUri!!)
this.binding.redgif.setText(contentUri)
} catch (e: Exception) {
Log.e(PackageManagerCompat.LOG_TAG, "Error: " + e)
Toast.makeText(this, "Error: " + e, Toast.LENGTH_SHORT).show()
}
}
}
}
binding.redloader.setOnClickListener {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
startActivity(Intent.createChooser(intent, "Open file"))
resultLauncher.launch(intent)
}

How to change the page programmicly in swiftui pdf

I have a code to display a pdf
import PDFKit
import SwiftUI
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let singlePage: Bool
var doc:PDFView=PDFView()
init(_ data: Data, singlePage: Bool = false) {
self.data = data
self.singlePage = singlePage
}
func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = doc
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
struct ContentView: View {
var path=Bundle.main.url(forResource: "big", withExtension: "pdf");
#State var doc:PDFKitRepresentedView=PDFKitRepresentedView(Data())
#State var data :Data?;
var body: some View {
HStack{
start(doc: &doc,path: path!)
.onAppear(perform: {
self.data=try?Data(contentsOf: path!);
doc.doc.goToNextPage(nil)
doc.doc.goToNextPage(nil)
})
}
}
}
func start(doc:inout PDFKitRepresentedView,path:URL)->PDFKitRepresentedView{
doc=try!PDFKitRepresentedView(Data(contentsOf: path));
return doc;
}
But I can't seem to find the method to change the page in the showing pdf.I tried go() but it just crashed. I need a button to change the page on click.Sorry I'm new to swiftUI.
you could re-structure your code and use the following approach in adding the page selection from the Buttons, into func updateUIView(...) :
import PDFKit
import SwiftUI
struct PDFKitView: UIViewRepresentable {
typealias UIViewType = PDFView
#Binding var page: Int
#State var data: Data
let singlePage: Bool
func makeUIView(context: UIViewRepresentableContext<PDFKitView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ view: UIViewType, context: UIViewRepresentableContext<PDFKitView>) {
if let thePage = view.document?.page(at: page) {
view.go(to: thePage)
}
}
}
struct ContentView: View {
#State var page = 0
var body: some View {
VStack {
HStack {
Button("Next Page") { page += 1 } // need to add bounds check
Button("Prev Page") { page -= 1 } // need to add bounds check
}.buttonStyle(.bordered)
PDFKitView(page: $page, data: loadData(), singlePage: true)
}
}
private func loadData() -> Data {
guard let path = Bundle.main.url(forResource: "big", withExtension: "pdf") else {
print("Could not find PDF document")
return Data()
}
do {
return try Data(contentsOf: path)
} catch {
print("error: \(error)") // todo
return Data()
}
}
}
EDIT-1:
Another somewhat more flexible approach is to pass a PDFDocument into PDFKitView, such as:
struct PDFKitView: UIViewRepresentable {
typealias UIViewType = PDFView
#Binding var page: Int
#Binding var doc: PDFDocument
let singlePage: Bool
func makeUIView(context: UIViewRepresentableContext<PDFKitView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = doc
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ view: UIViewType, context: UIViewRepresentableContext<PDFKitView>) {
view.document = doc
if let thePage = view.document?.page(at: page) {
view.go(to: thePage)
}
}
}
struct ContentView: View {
#State var page = 0
#State var doc = PDFDocument()
var body: some View {
VStack {
HStack {
Button("Next Page") {
if page + 1 < doc.pageCount {
page += 1
}
}
Button("Prev Page") {
if page-1 > 0 {
page -= 1
}
}
}.buttonStyle(.bordered)
PDFKitView(page: $page, doc: $doc, singlePage: true)
}
.onAppear {
loadDoc()
}
}
private func loadDoc() {
guard let path = Bundle.main.url(forResource: "big", withExtension: "pdf") else {
print("Could not find PDF document")
return
}
do {
let data = try Data(contentsOf: path)
if let theDoc = PDFDocument(data: data) {
doc = theDoc
}
} catch {
print("error: \(error)") // todo
}
}
}

How to make API call in swiftUI

I am trying to use PokeApi to make a Pokedex app. I just started swift a couple of days ago so I'm following a tutorial here: https://www.youtube.com/watch?v=UsO-84Xnhww. The tutorial doesn't seem to work, and I don't know how to access the PokeAPI in order to make this app. My code is posted below:
ContentView:
import SwiftUI
struct ContentView: View {
#State var searchText = ""
var pokemon = [Pokemon]()
var body: some View {
NavigationView {
List{
ForEach(searchText == "" ? pokemon : pokemon.filter({
$0.name.contains(searchText.lowercased())
})) { entry in
HStack {
Circle() //Pokemon Image
NavigationLink("\(entry.name)".capitalized, destination: Text("Detail view for \(entry.name)"))
}
}
}
.onAppear {
PokemonManager().getData() { pokemon in self.pokemon = pokemon
for pokemon in pokemon {
print(pokemon.name)
}
}
}
.searchable(text: $searchText)
.navigationTitle("PokePass")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
PokemonAPI:
import Foundation
struct CurrentPokemon: Codable {
var results: [Pokemon]
}
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
}
class PokemonManager {
func getData(completion: #escaping ([Pokemon]) -> ()) {
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon?limit=151") else {
return
}
URLSession.shared.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
let pokemonList = try! JSONDecoder().decode(CurrentPokemon.self, from: data)
DispatchQueue.main.async {
completion(pokemonList.results)
}
}
.resume()
}
}
Use let id = UUID() in Pokemon, this will avoid decoding it, and that is what you want, since id is not part of the data.
You can also use this approach:
struct Pokemon: Codable, Identifiable {
var id = UUID()
var name: String
var url: String
enum CodingKeys: String, CodingKey {
case name, url
}
}
EDIT-1
and use #State var pokemon = [Pokemon]() in ContentView

Why is my UICollectionView not loading cells?

I have a view controller in my app that plays user videos and you can scroll to the right to go to the next video. I have a cell set up that loads the video from the URL in firebase storage and loads the other data. Here is the cell code:
import UIKit
import AVFoundation
protocol ClipsCollectionViewCellDelegate: AnyObject {
func didTapProfile(with model: VideoModel)
func didTapShare(with model: VideoModel)
func didTapNewClip(with model: VideoModel)
}
class ClipsCollectionViewCell: UICollectionViewCell {
static let identifier = "ClipsCollectionViewCell"
// Labels
private let usernameLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.textColor = UIColor.systemPink.withAlphaComponent(0.5)
label.backgroundColor = UIColor.systemPink.withAlphaComponent(0.1)
label.clipsToBounds = true
label.layer.cornerRadius = 8
return label
}()
// Buttons
private let profileButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "person.circle"), for: .normal)
button.tintColor = .white
button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 32
button.isUserInteractionEnabled = true
return button
}()
private let shareButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "square.and.arrow.down"), for: .normal)
button.tintColor = .white
button.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 4
button.isUserInteractionEnabled = true
return button
}()
private let newClipButton: UIButton = {
let button = UIButton()
button.setBackgroundImage(UIImage(systemName: "plus"), for: .normal)
button.tintColor = .systemOrange
button.backgroundColor = UIColor.systemOrange.withAlphaComponent(0.1)
button.clipsToBounds = true
button.layer.cornerRadius = 25
button.isUserInteractionEnabled = true
return button
}()
private let videoContainer = UIView()
// Delegate
weak var delegate: ClipsCollectionViewCellDelegate?
// Subviews
var player: AVPlayer?
private var model: VideoModel?
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .black
contentView.clipsToBounds = true
addSubviews()
}
private func addSubviews() {
contentView.addSubview(videoContainer)
contentView.addSubview(usernameLabel)
contentView.addSubview(profileButton)
contentView.addSubview(shareButton)
contentView.addSubview(newClipButton)
// Add actions
profileButton.addTarget(self, action: #selector(didTapProfileButton), for: .touchUpInside)
shareButton.addTarget(self, action: #selector(didTapShareButton), for: .touchUpInside)
newClipButton.addTarget(self, action: #selector(didTapNewClipButton), for: .touchUpInside)
videoContainer.clipsToBounds = true
contentView.sendSubviewToBack(videoContainer)
}
#objc private func didTapProfileButton() {
guard let model = model else {
return
}
delegate?.didTapProfile(with: model)
}
#objc private func didTapShareButton() {
guard let model = model else {
return
}
delegate?.didTapShare(with: model)
}
#objc private func didTapNewClipButton() {
guard let model = model else {
return
}
delegate?.didTapNewClip(with: model)
}
override func layoutSubviews() {
super.layoutSubviews()
videoContainer.frame = contentView.bounds
let size = contentView.frame.size.width/7
let width = contentView.frame.size.width
let height = contentView.frame.size.height
// Labels
usernameLabel.frame = CGRect(x: (width-(size*3))/2, y: height-880-(size/2), width: size*3, height: size)
// Buttons
profileButton.frame = CGRect(x: width-(size*7), y: height-850-size, width: size, height: size)
shareButton.frame = CGRect(x: width-size, y: height-850-size, width: size, height: size)
newClipButton.frame = CGRect(x: width-size-10, y: height-175-size, width: size/1.25, height: size/1.25)
}
override func prepareForReuse() {
super.prepareForReuse()
usernameLabel.text = nil
player?.pause()
player?.seek(to: CMTime.zero)
}
public func configure(with model: VideoModel) {
self.model = model
configureVideo()
// Labels
usernameLabel.text = model.username
}
private func configureVideo() {
guard let model = model else {
return
}
guard let url = URL(string: model.videoFileURL) else { return }
player = AVPlayer(url: url)
let playerView = AVPlayerLayer()
playerView.player = player
playerView.frame = contentView.bounds
playerView.videoGravity = .resizeAspect
videoContainer.layer.addSublayer(playerView)
player?.volume = 0
player?.play()
player?.actionAtItemEnd = .none
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is the view controller code:
import UIKit
struct VideoModel {
let username: String
let videoFileURL: String
}
class BetaClipsViewController: UIViewController, UICollectionViewDelegate {
private var collectionView: UICollectionView?
private var data = [VideoModel]()
/// Notification observer
private var observer: NSObjectProtocol?
/// All post models
private var allClips: [(clip: Clip, owner: String)] = []
private var viewModels = [[ClipFeedCellType]]()
override func viewDidLoad() {
super.viewDidLoad()
title = ""
// for _ in 0..<10 {
// let model = VideoModel(username: "#CJMJM",
// videoFileURL: "https://firebasestorage.googleapis.com:443/v0/b/globe-e8b7f.appspot.com/o/clipvideos%2F1637024382.mp4?alt=media&token=c12d0481-f834-4a17-8eee-30595bdf0e8b")
// data.append(model)
// }
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: view.frame.size.width,
height: view.frame.size.height)
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
layout.minimumInteritemSpacing = 0
layout.minimumLineSpacing = 0
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView?.register(ClipsCollectionViewCell.self,
forCellWithReuseIdentifier: ClipsCollectionViewCell.identifier)
collectionView?.isPagingEnabled = true
collectionView?.delegate = self
collectionView?.dataSource = self
view.addSubview(collectionView!)
fetchClips()
observer = NotificationCenter.default.addObserver(
forName: .didPostNotification,
object: nil,
queue: .main
) { [weak self] _ in
self?.viewModels.removeAll()
self?.fetchClips()
}
self.collectionView?.reloadData()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
collectionView?.frame = view.bounds
}
private func fetchClips() {
guard let username = UserDefaults.standard.string(forKey: "username") else {
return
}
let userGroup = DispatchGroup()
userGroup.enter()
var allClips: [(clip: Clip, owner: String)] = []
DatabaseManager.shared.users(for: username) { usernames in
defer {
userGroup.leave()
}
let users = usernames + [username]
for current in users {
userGroup.enter()
DatabaseManager.shared.clips(for: current) { result in
DispatchQueue.main.async {
defer {
userGroup.leave()
}
switch result {
case .success(let clips):
allClips.append(contentsOf: clips.compactMap({
(clip: $0, owner: current)
}))
case .failure:
break
}
}
}
}
}
userGroup.notify(queue: .main) {
let group = DispatchGroup()
self.allClips = allClips
allClips.forEach { model in
group.enter()
self.createViewModel(
model: model.clip,
username: model.owner,
completion: { success in
defer {
group.leave()
}
if !success {
print("failed to create VM")
}
}
)
}
group.notify(queue: .main) {
self.collectionView?.reloadData()
}
}
}
}
extension BetaClipsViewController: UICollectionViewDataSource {
func numberOfSections(in collectionView: UICollectionView) -> Int {
return viewModels.count
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return viewModels[section].count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cellType = viewModels[indexPath.section][indexPath.row]
switch cellType {
case .clip(let viewModel):
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ClipsCollectionViewCell.identifier,
for: indexPath)
as? ClipsCollectionViewCell else {
fatalError()
}
cell.delegate = self
cell.configure(with: viewModel)
return cell
}
}
}
extension BetaClipsViewController: ClipsCollectionViewCellDelegate {
func didTapProfile(with model: VideoModel) {
print("profile tapped")
let owner = model.username
DatabaseManager.shared.findUser(username: owner) { [weak self] user in
DispatchQueue.main.async {
guard let user = user else {
return
}
let vc = ProfileViewController(user: user)
self?.navigationController?.pushViewController(vc, animated: true)
}
}
}
func didTapShare(with model: VideoModel) {
print("profile share")
}
func didTapNewClip(with model: VideoModel) {
let vc = RecordViewController()
navigationController?.pushViewController(vc, animated: true)
}
}
extension BetaClipsViewController {
func createViewModel(
model: Clip,
username: String,
completion: #escaping (Bool) -> Void
) {
StorageManager.shared.profilePictureURL(for: username) { [weak self] profilePictureURL in
guard let clipURL = URL(string: model.clipUrlString),
let profilePhotoUrl = profilePictureURL else {
return
}
let clipData: [ClipFeedCellType] = [
.clip(viewModel: VideoModel(username: username,
videoFileURL: model.clipUrlString))
]
self?.viewModels.append(clipData)
completion(true)
}
}
}
I think everything is set up properly in the code so why do no cells show in the collectionView? The view controller shows up as completely blank besides the background color.