watchOS 2 does not have any kind of completion block in its animateWithDuration function. I'm working on a game that requires running code after an animation is completed. Are there any work arounds? Perhaps using key-value observation? The alternative would be to use a timer that matches up with the length of the animation but that's non-ideal for obvious reasons.
NSOperation didn't work for me too. I am just using the following solution for now until Apple officially adds a completion block. Not ideal but it works. I have opened a Radar. Maybe Apple would add a completion block in a future seed of watchOS 2.
This extension adds an animateWithDuration method that takes a completion block. And the completion block is called after the duration of the animation block.
extension WKInterfaceController {
func animateWithDuration(duration: NSTimeInterval, animations: () -> Void, completion: (() -> Void)?) {
animateWithDuration(duration, animations: animations)
let completionDelay = dispatch_time(DISPATCH_TIME_NOW, Int64(duration * Double(NSEC_PER_SEC)))
dispatch_after(completionDelay, dispatch_get_main_queue()) {
completion?()
}
}
}
Try to extend animateWithDuration method with a completion block in your WKInterfaceController
Obj-C version (Thanks #PunnetSethi):
- (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(void))completion{
[self animateWithDuration:duration animations:animations];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), completion);
}
Working on real Watch with WatchOS2
Swift 4
advanced animate() method for watchOS:
Repeatable
Reversible
Stoppable
with completion block
import Foundation
import WatchKit
protocol Animatable: class {
func animate(forKey key: String,duration: TimeInterval, repeatCount count: CGFloat, autoreverses: Bool, animations: #escaping () -> Void, reverseAnimations: (() -> Void)?, completion: (() -> Void)?)
func stopAnimations()
}
extension Animatable {
func animate(forKey key: String, duration: TimeInterval, repeatCount count: CGFloat, autoreverses: Bool, animations: #escaping () -> Void, reverseAnimations: (() -> Void)?, completion: (() -> Void)?) {}
func stopAnimations() {}
}
extension WKInterfaceController: Animatable {
private struct AssociatedKeys {
static var animsDesc = "_animsDesc"
static var stopDesc = "_stopDesc"
}
var animations: [String: (() -> Void)?] {
get {return objc_getAssociatedObject(self, &AssociatedKeys.animsDesc) as? [String: (() -> Void)?] ?? [:]}
set {objc_setAssociatedObject(self, &AssociatedKeys.animsDesc, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
}
var animationStopStates: [String: Bool] {
get {return objc_getAssociatedObject(self, &AssociatedKeys.stopDesc) as? [String: Bool] ?? [:]}
set {objc_setAssociatedObject(self, &AssociatedKeys.stopDesc, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}
}
func animate(forKey key: String, duration: TimeInterval, repeatCount count: CGFloat = 1.0, autoreverses: Bool = false, animations: #escaping () -> Void, reverseAnimations: (() -> Void)? = nil, completion: (() -> Void)? = nil) {
let isStopped = self.animationStopStates[key] ?? false
if isStopped {
completion?()
return
}
self.setAnimations(key, reverse: reverseAnimations)
let count = count - 1
let deadline = DispatchTime.now() + duration
self.animate(withDuration: duration, animations: animations)
DispatchQueue.main.asyncAfter(deadline: deadline) {
var deadline2 = DispatchTime.now()
if autoreverses, let rev = reverseAnimations {
self.animate(withDuration: duration, animations: rev)
deadline2 = DispatchTime.now() + duration
}
DispatchQueue.main.asyncAfter(deadline: deadline2, execute: {
if !count.isEqual(to: .infinity) && count <= 0 {
completion?()
} else {
self.animate(forKey: key, duration: duration, repeatCount: CGFloat(count), autoreverses: autoreverses, animations: animations, reverseAnimations: reverseAnimations, completion: completion)
}
})
}
}
/// Stops all the currently playing animations
func stopAnimations() {
for key in self.animations.keys {
guard let rev = self.animations[key] else {return}
self.animationStopStates[key] = true
self.animate(forKey: key, duration: 0, repeatCount: 0, autoreverses: false, animations: {}, reverseAnimations: rev)
}
self.animations.removeAll()
}
private func setAnimations(_ key: String, reverse: (() -> Void)?) {
if self.animations[key] == nil {
self.animations[key] = reverse
}
}
}
How to use:
self.animate(forKey: "custom_anim1", duration: 1.0, repeatCount: .infinity, autoreverses: true, animations: {[unowned self] in
self.myButton.setHeight(100)
}, reverseAnimations: { [unowned self] in
self.myButton.setHeight(120)
}) {
print("Animation stopped!")
}
To stop all the animations:
self.stopAnimations()
You can use this extension. It's a better solutions because it doesn't rely on time like dispatching a block of code after the same amount of time as your animation with a delay. And it's elegant.
extension WKInterfaceController {
func animate(withDuration duration: TimeInterval,
animations: #escaping () -> Swift.Void,
completion: #escaping () -> Swift.Void) {
let queue = DispatchGroup()
queue.enter()
let action = {
animations()
queue.leave()
}
self.animate(withDuration: duration, animations: action)
queue.notify(queue: .main, execute: completion)
}
}
And you can use it, easily like this:
self.animate(withDuration: 0.2, animations: {
//your animation code
}, completion: {
//to do after you animation
})
Related
Application runs fine on iOS 12. But crashed on iOS 13.1. I think the issue is in the setting root view controller.
let storyboard = UIStoryboard(name: storyBoard, bundle: nil)
let desiredViewController = storyboard.instantiateViewController(withIdentifier: identifier);
self.window?.switchRootViewController(desiredViewController,animated: animate,options:options)
and the extension is:
extension UIWindow {
func switchRootViewController(_ viewController: UIViewController, animated: Bool = true, duration: TimeInterval = 0.3, options: UIView.AnimationOptions = .transitionFlipFromRight, completion: (() -> Void)? = nil) {
guard animated else {
rootViewController = viewController
return
}
UIView.transition(with: self, duration: duration, options: options, animations: {
let oldState = UIView.areAnimationsEnabled
UIView.setAnimationsEnabled(false)
self.rootViewController = viewController
UIView.setAnimationsEnabled(oldState)
}) { _ in
completion?()
}
}
}
I'm trying to flatten a recursive structure but I'm having trouble with recursive iterators.
Here's what the struct looks like:
#[derive(Debug, Clone)]
pub struct C {
name: String,
vb: Option<Vec<B>>,
}
#[derive(Debug, Clone)]
pub struct B {
c: Option<C>,
}
#[derive(Debug, Clone)]
pub struct A {
vb: Option<Vec<B>>,
flat_c: Option<Vec<C>>,
}
My plan is to traverse the vb vector and flatten it into flat_c. I want it to look like this, or at least, be a Vec<String>:
Some([
C {
name: "foo",
vb: None,
},
C {
name: "bar",
vb: None,
},
C {
name: "fizz",
vb: None,
},
C {
name: "buzz",
vb: None,
},
])
Here what I managed to do, somewhat flattening the struct, but only for the last element, as the recursion is not implemented.
impl A {
fn flat_c(self) -> Self {
let fc: Vec<C> = self
.vb
.clone()
.unwrap()
.iter()
.flat_map(|x| x.c.as_ref().unwrap().vb.as_ref().unwrap().iter())
.cloned()
.map(|x| x.c.unwrap())
.collect();
Self {
flat_c: Some(fc),
..self
}
}
}
fn main() {
let a = A {
vb: Some(vec![
B {
c: Some(C {
name: "foo".to_string(),
vb: Some(vec![B {
c: Some(C {
name: "bar".to_string(),
vb: None,
}),
}]),
}),
},
B {
c: Some(C {
name: "fiz".to_string(),
vb: Some(vec![B {
c: Some(C {
name: "buzz".to_string(),
vb: None,
}),
}]),
}),
},
]),
flat_c: None,
};
let a = a.flat_c();
println!("a: {:#?}", a);
}
playground
The output for flat_c:
Some([
C {
name: "bar",
vb: None,
},
C {
name: "buzz",
vb: None,
},
])
I haven't dived into the Iterator trait implementation that might be required for this problem.
How would I tackle this problem? Maybe using a fold? Perhaps a recursive approach is not even needed? I'm at loss.
It's a good idea to be familiar with common data structures. You have a tree, and there are several ways to traverse a tree. You haven't precisely specified which method to use, so I chose one arbitrarily that's easy to implement.
The key here is to implement an iterator that keeps track of some state: all of the nodes yet to be visited. On each call to Iterator::next, we take the next value, save aside any new nodes to visit, and return the value.
Once you have the iterator, you can collect it into a Vec.
use std::collections::VecDeque;
impl IntoIterator for A {
type IntoIter = IntoIter;
type Item = String;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
remaining: self.vb.into_iter().flatten().collect(),
}
}
}
struct IntoIter {
remaining: VecDeque<B>,
}
impl Iterator for IntoIter {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
self.remaining.pop_front().and_then(|b| {
b.c.map(|C { name, vb }| {
self.remaining.extend(vb.into_iter().flatten());
name
})
})
}
}
fn to_strings(a: A) -> Vec<String> {
a.into_iter().collect()
}
#[derive(Debug, Clone)]
struct A {
vb: Option<Vec<B>>,
}
#[derive(Debug, Clone)]
struct B {
c: Option<C>,
}
#[derive(Debug, Clone)]
struct C {
name: String,
vb: Option<Vec<B>>,
}
fn main() {
let example: A = A {
vb: Some(vec![
B {
c: Some(C {
name: "Hello ".to_string(),
vb: None,
}),
},
B {
c: Some(C {
name: "World!".to_string(),
vb: None,
}),
},
]),
};
println!("The example struct: {:?}", example);
//clone a copy for a second example, because to_strings() takes ownership of the example A struct
let receipt: A = example.clone();
println!("Iterated: {:?}", to_strings(example));
// another example of using to_strings()
println!(
"As a string: {:?}",
to_strings(receipt).into_iter().collect::<String>()
);
}
From here, it should be straight-forward to create an iterator of B if that's what you need. Having all of the None values seemed silly, so I left them out and directly returned Strings.
I also made this a by-value iterator. You could follow the same pattern to create an iterator that returned references to the B / String and only clone them as needed.
See also:
How to implement Iterator and IntoIterator for a simple struct?
Implement IntoIterator for binary tree
Cannot obtain a mutable reference when iterating a recursive structure: cannot borrow as mutable more than once at a time
Recursive inorder traversal of a binary search tree
There is my solution:
impl C {
fn flat(&self) -> Vec<C> {
let mut result = Vec::new();
result.push(C {
name: self.name.clone(),
vb: None,
});
if self.vb.is_some() {
result.extend(
(self.vb.as_ref().unwrap().iter())
.flat_map(|b| b.c.as_ref().map(|c| c.flat()).unwrap_or(Vec::new())),
);
}
return result;
}
}
impl A {
fn flat_c(self) -> Self {
let fc = (self.vb.as_ref().unwrap().iter())
.flat_map(|b| b.c.as_ref().unwrap().flat())
.collect();
Self {
flat_c: Some(fc),
..self
}
}
}
It adds flat function for C because the C is the source of the recursion and only this struct may properly handle it.
Because of those Options it looks scary and there is hard to deal with cryptic error messages. This solution supposes that all b.cs of initial a is not a None. Otherwise, it will panic. My advice is to avoid using Option<Vec> and use just empty vector instead of None.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=09ea11342cdd733b03172c0fc13c85fd
I'm not sure what exactly you want the result of "traverse the vb vector and flatten it into flat_c" to be, but here's a slightly simpler example of flattening a recursive structure, using once for the value that corresponds to the current node, chain to concatenate it with its children and flat_map to flatten everything:
use std::iter::once;
#[derive(Debug)]
struct S {
name: String,
children: Vec<S>,
}
impl S {
fn flat(self) -> Vec<String> {
once(self.name)
.chain(self.children.into_iter().flat_map(|c| c.flat()))
.collect()
}
}
fn main() {
let s = S {
name: "parent".into(),
children: vec![
S {
name: "child 1".into(),
children: vec![],
},
S {
name: "child 2".into(),
children: vec![],
},
],
};
println!("s: {:?}", s);
println!("flat: {:?}", s.flat());
}
playground
I have a function which returns Future. It accepts another function which accepts one argument and returns Future. Second function can be implemented as combinators chain passed into first function. It looks like this:
use bb8::{Pool, RunError};
use bb8_postgres::PostgresConnectionManager;
use tokio_postgres::{error::Error, Client, NoTls};
#[derive(Clone)]
pub struct DataManager(Pool<PostgresConnectionManager<NoTls>>);
impl DataManager {
pub fn new(pool: Pool<PostgresConnectionManager<NoTls>>) -> Self {
Self(pool)
}
pub fn create_user(
&self,
reg_req: UserRequest,
) -> impl Future<Item = User, Error = RunError<Error>> {
let sql = "long and awesome sql";
let query = move |mut conn: Client| { // function which accepts one argument and returns Future
conn.prepare(sql).then(move |r| match r {
Ok(select) => {
let f = conn
.query(&select, &[®_req.email, ®_req.password])
.collect()
.map(|mut rows| {
let row = rows.remove(0);
row.into()
})
.then(move |r| match r {
Ok(v) => Ok((v, conn)),
Err(e) => Err((e, conn)),
});
Either::A(f)
}
Err(e) => Either::B(future::err((e, conn))),
})
};
self.0.run(query) // function which returns Future and accepts another function
}
}
But I want to write code of create_user as a struct implementing Future.
struct UserCreator(Pool<PostgresConnectionManager<NoTls>>, UserRequest);
impl UserCreator {
fn new(pool: Pool<PostgresConnectionManager<NoTls>>, reg_req: UserRequest) -> Self {
Self(pool, reg_req)
}
}
How to implement Future for this struct that works as first function? Please help me with an example.
Now I tried to make it like this, but nothing is computed and execution always blocks.
impl Future for UserCreator {
type Item = User;
type Error = RunError<Error>;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
// Code which which works like `DataManager.create_user`
let sql = "long and awesome sql";
let reg_req = &self.1;
let query = move |mut conn: Client| {
conn.prepare(sql).then(move |r| match r {
Ok(select) => {
let f = conn
.query(&select, &[®_req.email, ®_req.password])
.collect()
.map(|mut rows| {
let row = rows.remove(0);
row.into()
})
.then(move |r| match r {
Ok(v) => Ok((v, conn)),
Err(e) => Err((e, conn)),
});
Either::A(f)
}
Err(e) => Either::B(future::err((e, conn))),
})
};
self.0.run(query).poll()
}
}
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.
I'm struggling with this Swift code already for some time and do not find the problem. The code
below should provide the File Directory as DataSource for a NSOutlineView. The GUI is quite simple
just a window with a NSOutlineView and a Object for the OutlineViewController instance.
When I start the application it shows the root entry, when I expand the root entry it shows for a short period the sub items. Then the application crashes with an Error in file "main.swift" at line "NSApplicationMain(C_ARGC, C_ARGV) --> "EXC_BAD_ACCESS(code=EXC_I386_GPFLT)" ?
If added some println() to proof the directory structure - this seems to be fine.
The swift code:
import Cocoa
import Foundation
class FileSystemItem {
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
let fileURL: NSURL
var name: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLNameKey], error: nil)
return resourceValues[NSURLNameKey] as? NSString
}
var localizedName: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLLocalizedNameKey], error: nil)
return resourceValues[NSURLLocalizedNameKey] as? NSString
}
var icon: NSImage! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLEffectiveIconKey], error: nil)
return resourceValues[NSURLEffectiveIconKey] as? NSImage
}
var dateOfCreation: NSDate! {
let resourceValues = self.fileURL.resourceValuesForKeys([NSURLCreationDateKey], error: nil)
return resourceValues[NSURLCreationDateKey] as? NSDate
}
var dateOfLastModification: NSDate! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLContentModificationDateKey], error: nil)
return resourceValues[NSURLContentModificationDateKey] as? NSDate
}
var typeIdentifier: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLTypeIdentifierKey], error: nil)
return resourceValues[NSURLTypeIdentifierKey] as? NSString
}
var isDirectory: String! {
let resourceValues = fileURL.resourceValuesForKeys([NSURLIsDirectoryKey], error: nil)
return resourceValues[NSURLIsDirectoryKey] as? NSString
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
var isDirectory: ObjCBool = ObjCBool(1)
let fileManager = NSFileManager.defaultManager()
var checkValidation = NSFileManager.defaultManager()
if (checkValidation.fileExistsAtPath(fileURL.relativePath)) {
if let itemURLs = fileManager.contentsOfDirectoryAtURL(fileURL, includingPropertiesForKeys:propertyKeys, options:.SkipsHiddenFiles, error:nil) {
for fsItemURL in itemURLs as [NSURL] {
if (fileManager.fileExistsAtPath(fsItemURL.relativePath, isDirectory: &isDirectory))
{
if(isDirectory == true) {
let checkItem = FileSystemItem(fileURL: fsItemURL)
childs.append(checkItem)
}
}
}
}
}
return childs
}
init (fileURL: NSURL) {
self.fileURL = fileURL
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutlineViewController : NSObject, NSOutlineViewDataSource {
let rootFolder : String = "/"
let rootfsItem : FileSystemItem
let fsItemURL : NSURL
let propertyKeys = [NSURLLocalizedNameKey, NSURLEffectiveIconKey, NSURLIsPackageKey, NSURLIsDirectoryKey,NSURLTypeIdentifierKey]
init() {
self.fsItemURL = NSURL.fileURLWithPath(rootFolder)
self.rootfsItem = FileSystemItem(fileURL: fsItemURL)
for fsItem in rootfsItem.children as [FileSystemItem] {
for fsSubItem in fsItem.children as [FileSystemItem] {
println("\(fsItem.name) - \(fsSubItem.name)")
}
}
}
func outlineView(outlineView: NSOutlineView!, numberOfChildrenOfItem item: AnyObject!) -> Int {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children.count
}
return 1
}
func outlineView(outlineView: NSOutlineView!, isItemExpandable item: AnyObject!) -> Bool {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.hasChildren()
}
return false
}
func outlineView(outlineView: NSOutlineView!, child index: Int, ofItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.children[index]
}
return rootfsItem
}
func outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject! {
if let theItem: AnyObject = item {
let tmpfsItem: FileSystemItem = item as FileSystemItem
return tmpfsItem.localizedName
}
return "-empty-"
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
Any hints ?
I had a similar problem with EXC_BAD_ACCESS on an NSOutlineView - with an NSOutlineViewDataSource. The same behaviour of as soon as the node was expanded, the data was displayed then the crash occurred. Some profiling in instruments showed that somewhere a Zombie object was created, and then the Outline view tried to access it.
I think this is a bug - but I managed to get around it by changing all Swift 'Strings' to 'NSStrings'. This may have to be done for all Swift types if you are using them.
In order to ensure everything was an NSString, I had to declare constants within the class such as:
var empty_string : NSString = ""
Because anytime I fed it a Swift string all hell broke loose. Oh well hopefully this will be fixed in the future!
So, just to clarify what is going on. NSOutlineView does not retain objects that it is given for its "model"; it was always expected that the client would retain them. For ARC code, this doesn't work well, because if you return a new instance to the NSOutlineView methods the object will not be retained by anything and will quickly be freed. Then subsequent outlineView delegate methods the touch these objects will lead to crashes. The solution to that is to retain the objects yourself in your own array.
Note that the objects returned from objectValueForTableColumn are retained by the NSControl's objectValue.
Back to Swift: As Thomas noted the objects have to be objc objects since they are bridged to an objc class. A Swift string is implicitly bridged to a temporary NSString. This leads to a crash because of the above issue, since nothing retains the NSString instance. That is why maintaining an array of NSStrings "solves" this problem.
The solution would be for NSOutlineView to have an option to retain the items given to it. Please consider logging a bug request for it to do this through bugreporter.apple.com
Thanks,
corbin (I work on NSOutlineView)
It seems that
outlineView(outlineView: NSOutlineView!, objectValueForTableColumn tableColumn: NSTableColumn!, byItem item: AnyObject!) -> AnyObject!
needs to return an object that conforms to obj-c protocol. So you can return
#objc class MyClass {
...
}
(or NSString and the like). But not native Swift stuff like String or Array etc.
I believe one of the problems going on here is the fact that the "children" array is getting replaced every time the children property is accessed.
I think this causes some weak references inside the NSOutlineView to break when it queries the DataSource for information.
If you cache the "children" and access the cache to compute "numberOfChildren" and "getChildForIndex" you should see an improvement.
In Swift 3.0 I used the following code, which compiles and runs without problems. It is far away from being complete but a step in the right direction, since I am trying to translate TreeTest into Swift.
import Cocoa
import Foundation
class FileSystemItem: NSObject {
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var fileURL: URL
var name: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.nameKey])
return resourceValues.name
}
var localizedName: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.localizedNameKey])
return resourceValues.localizedName
}
var icon: NSImage! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.effectiveIconKey])
return resourceValues.effectiveIcon as? NSImage
}
var dateOfCreation: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.creationDateKey])
return resourceValues.creationDate
}
var dateOfLastModification: Date! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.contentModificationDateKey])
return resourceValues.contentAccessDate
}
var typeIdentifier: String! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.typeIdentifierKey])
return resourceValues.typeIdentifier
}
var isDirectory: Bool! {
let resourceValues = try! fileURL.resourceValues(forKeys: [.isDirectoryKey])
return resourceValues.isDirectory
}
init(url: Foundation.URL) {
self.fileURL = url
}
var children: [FileSystemItem] {
var childs: [FileSystemItem] = []
let fileManager = FileManager.default
// show no hidden Files (if you want this, comment out next line)
// let options = FileManager.DirectoryEnumerationOptions.skipsHiddenFiles
var directoryURL = ObjCBool(false)
let validURL = fileManager.fileExists(atPath: fileURL.relativePath, isDirectory: &directoryURL)
if (validURL && directoryURL.boolValue) {
// contents of directory
do {
let childURLs = try
fileManager.contentsOfDirectory(at: fileURL, includingPropertiesForKeys: propertyKeys, options: [])
for childURL in childURLs {
let child = FileSystemItem(url: childURL)
childs.append(child)
}
}
catch {
print("Unexpected error occured: \(error).")
}
}
return childs
}
func hasChildren() -> Bool {
return self.children.count > 0
}
}
class OutLineViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource {
#IBOutlet weak var outlineView: NSOutlineView!
#IBOutlet weak var pathController: NSPathControl!
var fileSystemItemURL: URL!
let propertyKeys: [URLResourceKey] = [.localizedNameKey, .effectiveIconKey, .isDirectoryKey, .typeIdentifierKey]
var rootfileSystemItem: FileSystemItem!
var rootURL: URL!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let userDirectoryURL = URL(fileURLWithPath: NSHomeDirectory())
// directory "Pictures" is set as root
let rootURL = userDirectoryURL.appendingPathComponent("Pictures", isDirectory: true)
self.pathController.url = rootURL
self.rootfileSystemItem = FileSystemItem(url: rootURL)
for fileSystemItem in rootfileSystemItem.children as [FileSystemItem] {
for subItem in fileSystemItem.children as [FileSystemItem] {
print("\(fileSystemItem.name) - \(subItem.name)")
}
}
//FileSystemItem.rootItemWithPath(self.pathControl.URL.path)
//self.searchForFilesInDirectory(picturesPath)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func pathControllerAction(_ sender: NSPathControl) {
print("controller clicked")
}
// MARK: - outline data source methods
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children.count
}
return 1
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.hasChildren()
}
return false
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
if let fileSystemItem = item as? FileSystemItem {
return fileSystemItem.children[index]
}
return rootfileSystemItem
}
func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
if let fileSystemItem = item as? FileSystemItem {
switch tableColumn?.identifier {
case "tree"?:
return fileSystemItem.localizedName
case "coordinate"?:
return " empty "
default:
break
}
}
return " -empty- "
}
// MARK: - outline view delegate methods
func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
return false
}
}
With a new edit the outline view now shows all files and directories. You can influence the appearance in the children section in class FileSystemItem.