I'm using Swift 3
I have this social network like twitter and im trying to retrive the user's following list but i'm having some troubles, my idea is to get them so i can use them to then do a for loop and retrive the Posts or Tweets, so something like this
self.databaseRef.child("following").child((self.loggedInUser?.uid)!).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
for child in snapshot.children {
self.databaseRef.child("Jalas").child((child as AnyObject).key!).observe(.childAdded, with: { (snapshot:FIRDataSnapshot) in
if (snapshot.value as? NSDictionary != nil)
{
self.Jalas.append(snapshot.value as! NSDictionary)
}
}){(error) in
print(error.localizedDescription)
}
}
self.homeTableView.beginUpdates()
self.homeTableView.insertRows(at: [IndexPath(row:0,section:0)], with: UITableViewRowAnimation.automatic)
self.homeTableView.endUpdates()
self.aivLoading.stopAnimating()
}
and in number of rows in section i'm returning self.Jalas.count but this does nothing. and it throws me an error.
"attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update"
this is my database.
Jalas = Posts
{
"Jalas" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : {
"-KbHUnL-RveUQa3MPSWp" : {
"latitud" : "21.111401000574",
"longitud" : "-89.6112191677094",
"text" : "Fiesta en la Anahuac!!! ",
"timestamp" : "1485295269.30773",
"ubicacionN" : "Universidad Anáhuac Mayab"
},
"-KbI1azr6uFel-5uTZOD" : {
"latitud" : "Optional(21.018988764483463)",
"longitud" : "Optional(-89.614319546492695)",
"text" : "Hola chicos",
"timestamp" : "1485304393.77929",
"ubicacionN" : "Calle 53-A 341"
},
"-KbNQWxjQhc0Ce_ZQbq9" : {
"latitud" : "Optional(21.019219877217914)",
"longitud" : "Optional(-89.614173537203683)",
"text" : "Hola",
"timestamp" : "1485394812.83039",
"ubicacionN" : "Calle 53 341"
}
},
"mt0fzirhMhazIcy90MRWuRpTfmE2" : {
"-KbQOWfUnzY1JiS61J6-" : {
"latitud" : "Optional(21.111502615883129)",
"longitud" : "Optional(-89.611767497121221)",
"text" : "Hola chicos!",
"timestamp" : "1485444619.10931",
"ubicacionN" : "Carretera Mérida-Progreso 96"
}
}
},
"follower" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : {
"mt0fzirhMhazIcy90MRWuRpTfmE2" : true
},
"mt0fzirhMhazIcy90MRWuRpTfmE2" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : true
}
},
"following" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : {
"mt0fzirhMhazIcy90MRWuRpTfmE2" : true
},
"mt0fzirhMhazIcy90MRWuRpTfmE2" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : true
}
},
"handles" : {
"jcadmin" : "mt0fzirhMhazIcy90MRWuRpTfmE2",
"jcbest" : "dEXaVLDOSPfJa3zTyUNqAEtVuMR2"
},
"user_profiles" : {
"dEXaVLDOSPfJa3zTyUNqAEtVuMR2" : {
"about" : "Hola Mundo",
"handle" : "jcbest",
"name" : "Juan Carlos Estevez Rodriguez",
"profile_pic" : "https://firebasestorage.googleapis.com/v0/b/jalo-267da.appspot.com/o/user_profiles%2FOptional(%22dEXaVLDOSPfJa3zTyUNqAEtVuMR2%22)%2Fprofile_pic?alt=media&token=bfc3c516-7849-472c-b7cd-9668965a5dbe"
},
"mt0fzirhMhazIcy90MRWuRpTfmE2" : {
"about" : "Hola chicos",
"handle" : "jcadmin",
"name" : "Juan Carlos",
"profile_pic" : "https://firebasestorage.googleapis.com/v0/b/jalo-267da.appspot.com/o/user_profiles%2FOptional(%22mt0fzirhMhazIcy90MRWuRpTfmE2%22)%2Fprofile_pic?alt=media&token=b741b6c1-0bc5-446d-a1e5-159b21e770d2"
}
}
}
This is my entire code.
//
// HomeViewController.swift
// Twitter Clone
//
// Created by Juan Carlos Estevez on 15/12/16.
// Copyright © 2016 JC&C. All rights reserved.
//
import UIKit
import FirebaseDatabase
import FirebaseAuth
import SDWebImage
import ReadMoreTextView
class HomeViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UIScrollViewDelegate {
var databaseRef = FIRDatabase.database().reference()
var loggedInUser:AnyObject?
var loggedInUserData:NSDictionary?
var listFollowing = [NSDictionary?]()
var listFollowers = [NSDictionary?]()//store all the followers
#IBOutlet weak var aivLoading: UIActivityIndicatorView!
#IBOutlet weak var homeTableView: UITableView!
var defaultImageViewHeightConstraint:CGFloat = 77.0
var Jalas = [NSDictionary]()
override func viewDidLoad() {
super.viewDidLoad()
self.homeTableView.separatorStyle = .none
//self.homeTableView.backgroundView?.backgroundColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 1)
UITabBar.appearance().tintColor = UIColor.orange
navigationController?.navigationBar.barTintColor = UIColor.orange
Evento.sharedInstance.titulo = ""
Evento.sharedInstance.UbicacionN = nil
self.loggedInUser = FIRAuth.auth()?.currentUser
self.databaseRef.child("following").child((self.loggedInUser?.uid)!).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
for child in snapshot.children {
self.databaseRef.child("Jalas").child((child as AnyObject).key!).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
//if (snapshot.value as? NSDictionary != nil)
//{
self.Jalas.append(snapshot.value as! NSDictionary)
//}
}
}
//self.homeTableView.beginUpdates()
self.homeTableView.insertRows(at: [IndexPath(row:0,section:0)], with: UITableViewRowAnimation.automatic)
//self.homeTableView.endUpdates()
self.aivLoading.stopAnimating()
}
/*
//get the logged in users details
self.databaseRef.child("user_profiles").child(self.loggedInUser!.uid).observeSingleEvent(of: .value) { (snapshot:FIRDataSnapshot) in
//store the logged in users details into the variable
self.logdInUserData = snapshot.value as? NSDictionary
// print(self.loggedInUserData)
//get all the Jalas that are made by the user
self.databaseRef.child("Jalas").child(self.loggedInUser!.uid).observe(.childAdded, with: { (snapshot:FIRDataSnapshot) in
//self.Jalas.append(snapshot.value as! NSDictionary)
self.homeTableView.insertRows(at: [IndexPath(row:0,section:0)], with: UITableViewRowAnimation.automatic)
self.aivLoading.stopAnimating()
}){(error) in
print(error.localizedDescription)
}
}
*/
//when the user has no posts, stop animating the aiv after 5 seconds
Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(stopAnimating), userInfo: nil, repeats: false)
self.homeTableView.rowHeight = UITableViewAutomaticDimension
self.homeTableView.estimatedRowHeight = 140
/*
self.databaseRef.child("following").child(self.loggedInUser!.uid).observe(.childAdded, with: { (snapshot) in
let snapshot = snapshot.value as? NSDictionary
self.listFollowing.append(snapshot)
print(self.listFollowing)
}) { (error) in
print(error.localizedDescription)
}
self.databaseRef.child("followers").child(self.loggedInUser!.uid).observe(.childAdded, with: { (snapshot) in
let snapshot = snapshot.value as? NSDictionary
self.listFollowers.append(snapshot)
print(self.listFollowers)
}) { (error) in
print(error.localizedDescription)
}
*/
}
#IBAction func NewJalo(_ sender: Any) {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let welcomeViewController: UIViewController = mainStoryboard.instantiateViewController(withIdentifier: "new")
self.present(welcomeViewController, animated: true, completion: nil)
}
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
UITabBar.appearance().tintColor = UIColor.orange
return true
}
open func stopAnimating()
{
self.aivLoading.stopAnimating()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.Jalas.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.separatorStyle = .none
var ubicacion = ""
let cell: HomeViewTableViewCell = tableView.dequeueReusableCell(withIdentifier: "HomeViewTableViewCell", for: indexPath) as! HomeViewTableViewCell
cell.contentView.backgroundColor = UIColor.clear
let JaloPost = Jalas[(self.Jalas.count-1) - (indexPath.row)]["text"] as! String
var HG = 105
let CHAR = JaloPost.characters.count
if (CHAR > 30)
{
let CH = ((CHAR/30) * 15)
HG += CH
}
if(Jalas[(self.Jalas.count-1) - (indexPath.row)]["picture"] != nil)
{
HG += 77
}
let whiteRoundedView : UIView = UIView(frame: CGRect(x: 10, y: 8, width: Int(self.view.frame.size.width - 20), height: HG))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.9])
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 2.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
let imageTap = UITapGestureRecognizer(target: self, action: #selector(self.didTapMediaInTweet(_:)))
cell.tweetImage.addGestureRecognizer(imageTap)
if(Jalas[(self.Jalas.count-1) - (indexPath.row)]["ubicacionN"] != nil)
{
ubicacion = (Jalas[(self.Jalas.count-1) - (indexPath.row)]["ubicacionN"] as? String)!
}
if(Jalas[(self.Jalas.count-1) - (indexPath.row)]["picture"] != nil)
{
cell.tweetImage.isHidden = false
cell.imageViewHeightConstraint.constant = defaultImageViewHeightConstraint
let picture = Jalas[(self.Jalas.count-1) - (indexPath.row)]["picture"] as! String
let url = URL(string:picture)
cell.tweetImage.layer.cornerRadius = 10
cell.tweetImage.layer.borderWidth = 3
cell.tweetImage.layer.borderColor = UIColor.clear.cgColor
cell.tweetImage!.sd_setImage(with: url, placeholderImage: UIImage(named:"twitter")!)
}
else
{
cell.tweetImage.isHidden = true
cell.imageViewHeightConstraint.constant = 0
}
if (self.loggedInUserData!["profile_pic"] == nil)
{
cell.configure(nil,name:self.loggedInUserData!["name"] as! String,handle:self.loggedInUserData!["handle"] as! String,tweet:JaloPost, ubi: ubicacion)
}else
{
cell.configure(self.loggedInUserData!["profile_pic"]! as? String,name:self.loggedInUserData!["name"] as! String,handle:self.loggedInUserData!["handle"] as! String,tweet:JaloPost, ubi: ubicacion)
}
return cell
}
func didTapMediaInTweet(_ sender:UITapGestureRecognizer)
{
let imageView = sender.view as! UIImageView
let newImageView = UIImageView(image: imageView.image)
newImageView.frame = self.view.frame
newImageView.backgroundColor = UIColor.black
newImageView.contentMode = .scaleAspectFit
newImageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target:self,action:#selector(self.dismissFullScreenImage))
newImageView.addGestureRecognizer(tap)
self.view.addSubview(newImageView)
}
func dismissFullScreenImage(_ sender:UITapGestureRecognizer)
{
sender.view?.removeFromSuperview()
}
/*
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "findUserSegue")
{
var showFollowingTableViewController = segue.destination as! FollowUsersTableViewController
showFollowingTableViewController.loggedInUser = self.loggedInUser as? FIRUser
// showFollowingTableViewController.followData = self.followData
}
else if(segue.identifier == "showFollowersTableViewController")
{
var showFollowersTableViewController = segue.destination as! ShowFollowersTableViewController
showFollowersTableViewController.user = self.loggedInUser as? FIRUser
}
}
*/
}
And this is my Table View Cell code.
//
// HomeViewTableViewCell.swift
// Jalo
//
// Created by Juan Carlos Estevez Rodriguez on 24/08/16.
// Copyright © 2016 JC&C. All rights reserved.
//
import UIKit
open class HomeViewTableViewCell: UITableViewCell {
#IBOutlet weak var profilePic: UIImageView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var handle: UILabel!
#IBOutlet weak var tweet: UITextView!
#IBOutlet weak var tweetImage: UIImageView!
#IBOutlet weak var ubic: UILabel!
#IBOutlet weak var imageViewHeightConstraint: NSLayoutConstraint!
override open func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
open func configure(_ profilePic:String?,name:String,handle:String,tweet:String, ubi:String)
{
self.tweet.text = tweet
self.handle.text = "#"+handle
self.name.text = name
self.ubic.text = ubi
if((profilePic) != nil)
{
let imageData = try? Data(contentsOf: URL(string:profilePic!)!)
self.profilePic.image = UIImage(data:imageData!)
}
else
{
self.profilePic.image = UIImage(named:"twitter")
}
}
}
This
self.homeTableView.beginUpdates()
self.homeTableView.insertRows(at: [IndexPath(row:0,section:0)], with: UITableViewRowAnimation.automatic)
self.homeTableView.endUpdates()
Segment is what's most likely causing your problem. Won't be sure until you edit your question and add the exact error.
When you use .insertRows() in a UITableView, you need to make sure that the count in numberOfRows inSection method also changes accordingly. What you have here is just a mismatch between the number of rows returned by the delegate method and the actual number of rows. Make sure that when you use .insertRows() you return the new expected number of rows in the delegate method!
Related
As said in the title, I would like to show the location of my users on a map in my app. I would like something like Snapshat map :
Snapshat Map Picture
I used SwiftUI and Firebase.
Here is what I have done so far :
import SwiftUI
import Firebase
import CoreLocation
import MapKit
struct mapTimelineView: View {
#StateObject private var locationViewModel = LocationViewModel.shared
#State private var showNewPostView = false
#ObservedObject var viewModel = TimelineViewModel()
#ObservedObject var authViewModel = AuthViewModel()
#ObservedObject var obs = observer()
var body: some View {
ZStack (alignment: .bottomTrailing) {
// Map(coordinateRegion: $locationViewModel.region, showsUserLocation: true)
// .accentColor(Color("accentColor"))
// .edgesIgnoringSafeArea(.all)
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint]) <--- /!\ the problem occurs here /!\
Button {
showNewPostView.toggle()
} label: {
Image(systemName: "plus")
.resizable()
.renderingMode(.template)
.frame(width: 30, height: 30)
.font(.system(size: 30, weight: .bold, design: .default))
.padding()
}
.background(Color("accentColor"))
.foregroundColor(Color("backgroundColor"))
.clipShape(Circle())
.padding()
.shadow(radius: 20)
.fullScreenCover(isPresented: $showNewPostView) {
uploadPostView()
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("home")
.background(Color("backgroundColor"))
}
}
struct mapTimelineView_Previews: PreviewProvider {
static var previews: some View {
mapTimelineView()
}
}
struct mapView: UIViewRepresentable {
#ObservedObject var authViewModel = AuthViewModel()
var geopoints : [String: GeoPoint]
func makeCoordinator() -> Coordinator {
return mapView.Coordinator(parent1: self)
}
let map = MKMapView()
let manager = CLLocationManager()
func makeUIView(context: Context) -> MKMapView {
manager.delegate = context.coordinator
manager.startUpdatingLocation()
map.showsUserLocation = true
let region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 48.856614, longitude: 2.3522219), span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
map.region = region
return map
}
func updateUIView(_ uiView: MKMapView, context: Context) {
for i in geopoints {
let point = MKPointAnnotation()
point.coordinate = CLLocationCoordinate2D(latitude: i.value.latitude, longitude: i.value.longitude)
point.title = i.key
uiView.removeAnnotations(uiView.annotations)
uiView.addAnnotation(point)
}
}
class Coordinator: NSObject, CLLocationManagerDelegate {
#ObservedObject var authViewModel = AuthViewModel()
var parent: mapView
init(parent1: mapView) {
parent = parent1
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let uid = self.authViewModel.userSession?.uid else { return }
let last = locations.last
Firestore.firestore().collection("locations").document("coordinate").setData(["updates" : [uid : GeoPoint(latitude: (last?.coordinate.latitude)!, longitude: (last?.coordinate.longitude)!)]],merge: true) { (err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
print("success")
}
}
}
}
class observer : ObservableObject{
#Published var data = [String : Any]()
init() {
let db = Firestore.firestore()
db.collection("locations").document("coordinate").addSnapshotListener { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let updates = snap?.get("updates") as! [String : GeoPoint]
self.data["data"] = updates
}
}
}
It shows a map and the user location.
But my problem is that my app crash because there is "nil" on this line :
mapView(geopoints: self.obs.data["data"] as! [String : GeoPoint])
I got this error message :
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
And, I don't understand this issue because I do have data in my database :
So, if anyone has a solution, I would like to know it.
Despite the simplicity of the question I have not been able to find a satisfactory answer yet. I want to update toggle switches based on value in Firebase. I have added listeners but run into problems converting a Bool to Binding Bool, any help is appreciated.
struct oneSeqeuncer : Identifiable{
var id: String
var status: Bool
}
struct Sequencers: View {
#ObservedObject var seqModel = SequencerModel()
#State private var novaseq404A :Bool = true
#State private var novaseq404B :Bool = true
#State private var novaseq297A :Bool = true
#State private var novaseq297B :Bool = true
var body: some View {
ZStack{
VStack{
Text("Sequencers")
.foregroundColor(.white)
.font(.title)
.fontWeight(.bold)
.padding()
List{
HStack{
Text("404")
.font(.title)
.padding()
Toggle("", isOn: $novaseq404A)
.onChange(of: novaseq404A) { newValue in
updateStatus(name: "404A", status: novaseq404A)
}
Toggle("", isOn: $novaseq404B)
.padding()
.onChange(of: novaseq404B) { newValue in
updateStatus(name: "404B", status: novaseq404B)
}
}
HStack{
Text("297")
.font(.title)
.padding()
Toggle("", isOn: $novaseq297A)
.onChange(of: novaseq297A) { newValue in
updateStatus(name: "297A", status: novaseq297A)
}
Toggle("", isOn: $novaseq297B)
.padding()
.onChange(of: novaseq297B) { newValue in
updateStatus(name: "297B", status: novaseq297B)
}
}
}
}
}.onAppear(){
self.seqModel.fetchData()
for seq in seqModel.seqs{
if seq.id == "404A"{
novaseq404A = seq.status
}
if seq.id == "404B"{
novaseq404A = seq.status
}
if seq.id == "297A"{
novaseq297A = seq.status
}
if seq.id == "297B"{
novaseq297B = seq.status
}
}
func updateStatus(name: String, status: Bool){
let timeInterval = NSDate().timeIntervalSince1970
let myInt = Int(timeInterval)
let db = Firestore.firestore()
if status == false{
db.collection("Sequencers").document(name).updateData([
"status": false,
"lastChange" : myInt
]){ error in
if error != nil{
print(error!)
}
}
}
else{
let docRef = db.collection("Sequencers").document(name)
docRef.getDocument {(document, error) in
if error != nil {
print(error!)
}
if let document = document, document.exists{
let data = document.data()
if let lastChange = data!["lastChange"]! as? Int{
let timeOff = myInt - lastChange
if let timeOffTotal = data!["timeOff"]! as? Int{
let newTimeOff = timeOffTotal + timeOff
db.collection("Sequencers").document(name).updateData([
"timeOff" : newTimeOff
])
}
}
db.collection("Sequencers").document(name).updateData([
"previousChange": data!["lastChange"]!,
"status": true ,
"lastChange" : myInt
])
}
}
}
}
}
struct Sequencers_Previews: PreviewProvider {
static var previews: some View {
Sequencers()
}
}
Below is my model for storing 'sequencers'
import Foundation
import FirebaseFirestore
import Firebase
class SequencerModel : ObservableObject {
#Published var seqs = [oneSeqeuncer]()
private var db = Firestore.firestore()
func fetchData(){
db.collection("Sequencers").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.seqs = documents.map { queryDocumentSnapshot -> oneSeqeuncer in
let data = queryDocumentSnapshot.data()
let id = queryDocumentSnapshot.documentID
let status = data["status"] as? Bool
print(id)
print(status as Any)
return oneSeqeuncer(id: id, status: status!)
}
}
}
}
My solution was not ideal but solved, I realized the function .fetchData() that I was calling to was taking too long to respond. Ideally I should use some completion handler... However I simply changed my TabView on ContentView to display another page first, to allow time for my call to Firebase to finish, which allowed my for loop in .onAppear to have a non empty iterable. Again not sure this really belongs as an "Answer" but just wanted to share my temp solution as an option.
I have this view:
import SwiftUI
struct SectionView1: View {
let dateStr:String
#Binding var isSectionView:Bool
var body: some View {
HStack {
Button(action: {
self.isSectionView.toggle()
}) {
Image(systemName: isSectionView ? "chevron.down.circle" : "chevron.right.circle")
}
Text("Media del \(dateStr)")
}
}
}
which will be called from view:
import SwiftUI
import Photos
struct MediaView: View {
let geoFolder:GeoFolderCD
#State private var assetsForDate = [String :[PHAsset]]()
#State private var isSectionViewArray:[String:Bool] = [:]
var body: some View {
List {
ForEach(assetsForDate.keys.sorted(by: > ), id: \.self) { dateStr in
Section {
SectionView1(dateStr: dateStr,
isSectionView: self.$isSectionViewArray[dateStr, default: true])
}
}
}
.onAppear {
self.assetsForDate = FetchMediaUtility().fetchGeoFolderAssetsForDate(geoFolder: geoFolderStruct, numAssets: numMediaToFetch)
for dateStr in self.assetsForDate.keys.sorted() {
self.isSectionViewArray[dateStr] = true
}
}
}
}
but I have the error: Subscript index of type '() -> Bool' in a key path must be Hashable in isSectionView: self.$isSectionViewArray[dateStr, default: true]
Why isSectionViewArray:[String:Bool] = [:] is not Hasbable?
How can modify the code for work?
If I remove, in SectionView, #Binding var isSectionView:Bool, the code work fine, or if I set, from SectionView, #Binding var isSectionViewArray:[String:Bool] = [:], the code work fine.
You can write your own binding with the below code and it should work
var body: some View {
List {
ForEach(assetsForDate.keys.sorted(by: > ), id: \.self) { dateStr in
let value = Binding<Bool>(get: { () -> Bool in
return self.isSectionViewArray[dateStr, default: true]
}) { (value) in
}
Section {
SectionView1(dateStr: dateStr,
isSectionView: value)
}
}
}
.onAppear {
self.assetsForDate = FetchMediaUtility().fetchGeoFolderAssetsForDate(geoFolder: geoFolderStruct, numAssets: numMediaToFetch)
for dateStr in self.assetsForDate.keys.sorted() {
self.isSectionViewArray[dateStr] = true
}
}
}
I am trying to create an app which will have a list of announcements, connected to the Firebase server for testing and if the Firebase has a pdf attribute i want to display it in the App.
The code for this is below:
import UIKit
import FirebaseDatabaseUI
import Firebase
import Down
import FontAwesomeIconFactory
import PDFKit
extension AnnouncementDetailViewController: PDFViewDelegate {
func pdfViewWillClick(onLink sender: PDFView, with url: URL){
print(url)
}
}
class AnnouncementDetailViewController: UIViewController {
#IBOutlet var authorLabel: UILabel!
#IBOutlet var titleLabel: UILabel!
#IBOutlet var authorImage: UIImageView!
var announcementKey = ""
let announcement: Announcement = Announcement()
lazy var ref: DatabaseReference = Database.database().reference()
var announcementRef: DatabaseReference!
var refHandle: DatabaseHandle?
var contentView: DownView?
var pdfView: PDFView!
override func viewDidLoad() {
super.viewDidLoad()
initialiseContentView()
initialseDatabaseChild()
initialiseNavbarTitle()
//Method calls for Pdf
configureUI()
loadPDF()
playWithPDF()
}
private func configureUI(){
pdfView = PDFView ()
pdfView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(pdfView)
pdfView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
pdfView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
pdfView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
pdfView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
pdfView.delegate = self
}
private func addObservers(){
NotificationCenter.default.addObserver(self, selector: #selector(handlePageChange(notification:)), name: Notification.Name.PDFViewPageChanged, object: nil)
}
#objc private func handlePageChange(notification: Notification){
print("Current page is changed")
}
private func loadPDF() {
guard
let url = URL(string:"http://www.pdf995.com/samples/pdf.pdf"),
let document = PDFDocument(url: url)
else { return }
if document.isLocked && document.unlock(withPassword: "test"){
pdfView.document = document
} else {
print("We have a problem")
}
}
private func playWithPDF(){
pdfView.displayMode = .singlePageContinuous
pdfView.autoScales = true
}
private func initialiseNavbarTitle() {
self.navigationItem.title = "Announcement Detail"
}
private func initialseDatabaseChild() {
announcementRef = ref.child("announcements").child(announcementKey)
}
private func generateDownViewHeight() -> CGFloat {
return UIScreen.main.bounds.height - authorLabel.frame.height - authorImage.frame.height - (navigationController?.navigationBar.frame.height ?? 0) - view.safeAreaInsets.top - 70
}
private func initialiseContentView() {
guard let contentView = try? DownView(frame: CGRect(x: 0, y: 0, width: 0, height: 0), markdownString: "") else { return }
view.addSubview(contentView)
contentView.scrollView.bounces = false
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 5).isActive = true
contentView.heightAnchor.constraint(equalToConstant: generateDownViewHeight()).isActive = true
contentView.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width).isActive = true
self.contentView = contentView
}
#objc override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refHandle = announcementRef.observe(DataEventType.value, with: { (snapshot) in
let announcementDict = snapshot.value as? [String : AnyObject] ?? [:]
self.announcement.setValuesForKeys(announcementDict)
do {
try self.contentView?.update(markdownString: self.announcement.content)
let factory = FontAwesomeIconFactory.button()
self.authorImage.image = factory.createImage(NIKFontAwesomeIcon.male)
self.authorLabel.text = self.announcement.author
self.titleLabel.text = self.announcement.title
}
catch {
}
})
}
I got some error when I send photo message.
I'm a beginner with swift language. I want to create undergraduate project about chat app. I need some help to fix this problem. Everything had worked smoothly till now but I am facing a small bug that is when I receive an image then it don't appears on the left side of the screen both incoming and outgoing images appear on the right side of the screen.
import UIKit
import JSQMessagesViewController
import MobileCoreServices
import AVKit
import FirebaseDatabase
import FirebaseStorage
import FirebaseAuth
import SDWebImage
class ChatViewController: JSQMessagesViewController {
var messages = [JSQMessage]()
var avatarDict = [String: JSQMessagesAvatarImage]()
var messageRef = FIRDatabase.database().reference().child("messages")
override func viewDidLoad() {
super.viewDidLoad()
if let currentUser = FIRAuth.auth()?.currentUser
{
self.senderId = currentUser.uid
if currentUser.isAnonymous == true
{
self.senderDisplayName = "anonymous"
} else
{
self.senderDisplayName = "\(currentUser.displayName!)"
}
}
observeMessages()
}
func observeUsers(_ id: String)
{
FIRDatabase.database().reference().child("users").child(id).observe(.value, with: {
snapshot in
if let dict = snapshot.value as? [String: AnyObject]
{
let avatarUrl = dict["profileUrl"] as! String
self.setupAvatar(avatarUrl, messageId: id)
}
})
}
func setupAvatar(_ url: String, messageId: String)
{
if url != "" {
let fileUrl = URL(string: url)
let data = try? Data(contentsOf: fileUrl!)
let image = UIImage(data: data!)
let userImg = JSQMessagesAvatarImageFactory.avatarImage(with: image, diameter: 30)
self.avatarDict[messageId] = userImg
self.collectionView.reloadData()
} else {
avatarDict[messageId] = JSQMessagesAvatarImageFactory.avatarImage(with: UIImage(named: "profileImage"), diameter: 30)
collectionView.reloadData()
}
}
func observeMessages() {
messageRef.observe(.childAdded, with: { snapshot in
// print(snapshot.value)
if let dict = snapshot.value as? [String: AnyObject] {
let mediaType = dict["MediaType"] as! String
let senderId = dict["senderId"] as! String
let senderName = dict["senderName"] as! String
self.observeUsers(senderId)
switch mediaType {
case "TEXT":
let text = dict["text"] as! String
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName, text: text))
case "PHOTO":
let photo = JSQPhotoMediaItem(image: nil)
let fileUrl = dict["fileUrl"] as! String
let downloader = SDWebImageDownloader.shared()
downloader.downloadImage(with: URL(string: fileUrl)!, options: [], progress: nil, completed: { (image, data, error, finished) in
DispatchQueue.main.async(execute: {
photo?.image = image
self.collectionView.reloadData()
})
})
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName, media: photo))
if self.senderId == senderId {
photo?.appliesMediaViewMaskAsOutgoing = true
} else {
photo?.appliesMediaViewMaskAsOutgoing = false
}
case "VIDEO":
let fileUrl = dict["fileUrl"] as! String
let video = URL(string: fileUrl)!
let videoItem = JSQVideoMediaItem(fileURL: video, isReadyToPlay: true)
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName, media: videoItem))
if self.senderId == senderId {
videoItem?.appliesMediaViewMaskAsOutgoing = true
} else {
videoItem?.appliesMediaViewMaskAsOutgoing = false
}
default:
print("unknown data type")
}
self.collectionView.reloadData()
}
})
}
override func didPressSend(_ button: UIButton!, withMessageText text: String!, senderId: String!, senderDisplayName: String!, date: Date!) {
let newMessage = messageRef.childByAutoId()
let messageData = ["text": text, "senderId": senderId, "senderName": senderDisplayName, "MediaType": "TEXT"]
newMessage.setValue(messageData)
self.finishSendingMessage()
}
override func didPressAccessoryButton(_ sender: UIButton!) {
print("didPressAccessoryButton")
let sheet = UIAlertController(title: "Media Messages", message: "Please select a media", preferredStyle: UIAlertControllerStyle.actionSheet)
let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) { (alert:UIAlertAction) in
}
let photoLibrary = UIAlertAction(title: "Photo Library", style: UIAlertActionStyle.default) { (alert: UIAlertAction) in
self.getMediaFrom(kUTTypeImage)
}
let videoLibrary = UIAlertAction(title: "Video Library", style: UIAlertActionStyle.default) { (alert: UIAlertAction) in
self.getMediaFrom(kUTTypeMovie)
}
sheet.addAction(photoLibrary)
sheet.addAction(videoLibrary)
sheet.addAction(cancel)
self.present(sheet, animated: true, completion: nil)
}
func getMediaFrom(_ type: CFString) {
print(type)
let mediaPicker = UIImagePickerController()
mediaPicker.delegate = self
mediaPicker.mediaTypes = [type as String]
self.present(mediaPicker, animated: true, completion: nil)
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageDataForItemAt indexPath: IndexPath!) -> JSQMessageData! {
return messages[indexPath.item]
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, messageBubbleImageDataForItemAt indexPath: IndexPath!) -> JSQMessageBubbleImageDataSource! {
let message = messages[indexPath.item]
let bubbleFactory = JSQMessagesBubbleImageFactory()
if message.senderId == self.senderId {
return bubbleFactory!.outgoingMessagesBubbleImage(with: .black)
} else {
return bubbleFactory!.incomingMessagesBubbleImage(with: .blue)
}
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, avatarImageDataForItemAt indexPath: IndexPath!) -> JSQMessageAvatarImageDataSource! {
let message = messages[indexPath.item]
return avatarDict[message.senderId]
//return JSQMessagesAvatarImageFactory.avatarImageWithImage(UIImage(named: "profileImage"), diameter: 30)
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
print("number of item:\(messages.count)")
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell
return cell
}
override func collectionView(_ collectionView: JSQMessagesCollectionView!, didTapMessageBubbleAt indexPath: IndexPath!) {
print("didTapMessageBubbleAtIndexPath: \(indexPath.item)")
let message = messages[indexPath.item]
if message.isMediaMessage {
if let mediaItem = message.media as? JSQVideoMediaItem {
let player = AVPlayer(url: mediaItem.fileURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true, completion: nil)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func logoutDidTapped(_ sender: AnyObject) {
do {
try FIRAuth.auth()?.signOut()
} catch let error {
print(error)
}
// Create a main storyboard instance
let storyboard = UIStoryboard(name: "Main", bundle: nil)
// From main storyboard instantiate a View controller
let LogInVC = storyboard.instantiateViewController(withIdentifier: "LogInVC") as! LogInViewController
// Get the app delegate
let appDelegate = UIApplication.shared.delegate as! AppDelegate
// Set LogIn View Controller as root view controller
appDelegate.window?.rootViewController = LogInVC
}
func sendMedia(_ picture: UIImage?, video: URL?) {
print(picture)
print(FIRStorage.storage().reference())
if let picture = picture {
let filePath = "\(FIRAuth.auth()!.currentUser)/\(Date.timeIntervalSinceReferenceDate)"
print(filePath)
let data = UIImageJPEGRepresentation(picture, 0.1)
let metadata = FIRStorageMetadata()
metadata.contentType = "image/jpg"
FIRStorage.storage().reference().child(filePath).put(data!, metadata: metadata) { (metadata, error)
in
if error != nil {
print(error?.localizedDescription)
return
}
let fileUrl = metadata!.downloadURLs![0].absoluteString
let newMessage = self.messageRef.childByAutoId()
let messageData = ["fileUrl": fileUrl, "senderId": self.senderId, "senderName": self.senderDisplayName, "MediaType": "PHOTO"]
newMessage.setValue(messageData)
}
} else if let video = video {
let filePath = "\(FIRAuth.auth()!.currentUser)/\(Date.timeIntervalSinceReferenceDate)"
print(filePath)
let data = try? Data(contentsOf: video)
let metadata = FIRStorageMetadata()
metadata.contentType = "video/mp4"
FIRStorage.storage().reference().child(filePath).put(data!, metadata: metadata) { (metadata, error)
in
if error != nil {
print(error?.localizedDescription)
return
}
let fileUrl = metadata!.downloadURLs![0].absoluteString
let newMessage = self.messageRef.childByAutoId()
let messageData = ["fileUrl": fileUrl, "senderId": self.senderId, "senderName": self.senderDisplayName, "MediaType": "VIDEO"]
newMessage.setValue(messageData)
}
}
}
}
extension ChatViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
print("did finish picking")
// get the image
print(info)
if let picture = info[UIImagePickerControllerOriginalImage] as? UIImage {
sendMedia(picture, video: nil)
}
else if let video = info[UIImagePickerControllerMediaURL] as? URL {
sendMedia(nil, video: video)
}
self.dismiss(animated: true, completion: nil)
collectionView.reloadData()
}
}
You just need to understand what is going on in your code , as i am looking into your code you are reloading collectionview before the JSQPhotoMediaItem or JSQVideoMediaItem objects are completely configured and added these MediaItem into your message array, so make sure you have completely configured your JSQPhotoMediaItem and JSQVideoMediaItem objects and only after that you add these object to your message array.
you can add this into your switch case condition
case "PHOTO":
let photo = JSQPhotoMediaItem(image: nil)
let fileUrl = dict["fileUrl"] as! String
let downloader = SDWebImageDownloader.shared()
downloader.downloadImage(with: URL(string: fileUrl)!, options: [], progress: nil, completed: { (image, data, error, finished) in
DispatchQueue.main.async(execute: {
photo?.image = image // you have image in your media object
if self.senderId == senderId {
photo?.appliesMediaViewMaskAsOutgoing = true
} else {
photo?.appliesMediaViewMaskAsOutgoing = false
}
// you just configured media object by using appliesMediaViewMaskAsOutgoing
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName, media: photo))
// successfully added object into your message array now you should reload collectionview
self.collectionView.reloadData()
})
})
//same goes for your video condition
But still i would recommend you to follow each step one by one.
Follow these steps for adding A JSQPhotoMediaItem object
make a JSQPhotoMediaItem Object like
let photoItem = JSQPhotoMediaItem(image: UIImage(named: <your Image Object>))
make sure you have downloaded your image from server and successfully have your image object ready to add in the JSQPhotoMediaItem Object (as you are downloading it from firebase) first get the image then add it to your JSQPhotoMediaItem
now you have to tell your message type( outgoing or incoming ) so add appliesMediaViewMaskAsOutgoing property to according to you need so this would be in your code
if self.senderId == senderId {
photoItem?.appliesMediaViewMaskAsOutgoing = true
} else {
photoItem?.appliesMediaViewMaskAsOutgoing = false
}
now this is the final step for appending this object to your array
self.messages.append(JSQMessage(senderId: senderId, displayName: senderName, media: photoItem))
now reload collectionView as you have successfully configured your media item ( JSQPhotoMediaItem )
self.collectionView.reloadData()
same goes for the JSQVideoMediaItem objects.