I have a video player in my app. There is a list of videos in a collection view. If you tap on one of the cells, a new view controller appears to play the selected video. Also, you can cycle through all of the videos from the collection view in this new view controller because the entire list is passed.
The problem is:
When the user is in the PlayerVC
, they can unfavorite a Video
. If they do this, I delete the Video
object from Realm. However, this causes a:
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
Basically, if a user is watching a video in PlayerVC
and he unfavorites a video, I want them to still be able to watch the video for the time being. But when they leave the PlayerVC
, the collection view in FavoritesVC
should be updated and not show that Video
any more.
When I delete a Video
object I use Realm's delete
method.
This is my code to hold a list of Video
objects:
/// Model class that manages the ordering of `Video` objects.
final class FavoriteList: Object {
// MARK: - Properties
/// `objectId` is set to a static value so that only
/// one `FavoriteList` object could be saved into Realm.
dynamic var objectId = 0
let videos = List<Video>()
// MARK: - Realm Meta Information
override class func primaryKey() -> String? {
return "objectId"
}
}
This is my Video
class which has an isFavorite
property:
final class Video: Object {
// MARK: - Properties
dynamic var title = ""
dynamic var descriptionText = ""
dynamic var date = ""
dynamic var videoId = ""
dynamic var category = ""
dynamic var duration = 0
dynamic var fullURL = ""
dynamic var creatorSite = ""
dynamic var creatorName = ""
dynamic var creatorURL = ""
// MARK: FileManager Properties (Files are stored on disk for `Video` object).
/*
These are file names (e.g., myFile.mp4, myFile.jpg)
*/
dynamic var previewLocalFileName: String?
dynamic var stitchedImageLocalFileName: String?
dynamic var placeholderLocalFileName: String?
/*
These are partial paths (e.g., bundleID/Feed/myFile.mp4, bundleID/Favorites/myFile.mp4)
They are used to build the full path/URL at runtime.
*/
dynamic var previewLocalFilePath: String?
dynamic var stitchedImageLocalFilePath: String?
dynamic var placeholderLocalFilePath: String?
// Other code...
}
This is my code to show the Video
objects in a collection view (Note: I use RealmCollectionChange
to update the collection view for deleting and inserting cells):
/// This view controller has a `collectioView` to show the favorites.
class FavoriteCollectionViewController: UIViewController {
// MARK: Properties
let favoriteList: FavoriteList = {
let realm = try! Realm()
return realm.objects(FavoriteList.self).first!
}()
// Realm notification token to update collection view.
var notificationToken: NotificationToken?
// MARK: Collection View
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return favoriteList.videos.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FavoritesCollectionViewCell.reuseIdentifier, for: indexPath) as! FavoritesCollectionViewCell
cell.video = favoriteList.videos[indexPath.item]
return cell
}
// I pass this lst forward to the PlayerVC
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if let playerVC = self.storyboard?.instantiateViewController(withIdentifier: "PlayerViewController") as? PlayerViewController {
// I pass the videos here.
playerVC.videos = favoriteList.videos
self.parent?.present(playerVC, animated: true, completion: nil)
}
}
// MARK: Realm Notifications
func updateUI(with changes: RealmCollectionChange<List<Video>>) {
// This is code to update the collection view.
}
}
Finally, this is the code to allow the user to play and cycle between all Video
objects:
/// This view controller uses `AVFoundation` to play the videos from `FavoriteCollectionViewController`.
class PlayerViewControllerr: UIViewController {
/// This `videos` is passed from `FavoriteCollectionViewController`
var videos = List<Video>()
// HELP: The app crashes here if I unfavorite a `Video`.
@IBAction func didToggleStarButton(_ sender: UIButton) {
let realm = try! Realm()
try! realm.write {
let videoToDelete = videos[currentIndexInVideosList] /// Get the video that is currently playing
realm.delete(videoToDelete)
}
}
}
Ultimately I want the Video
object that is unfavorited to be completely deleted from Realm. Just not sure how/when to do it in this case.
Any thoughts?
Update 1
An option to solve this is to:
- Make an unmanaged copy of the
Video
copy, and use that one to power the UI of the view controller.
The way I see this possibly working is this:
The
PlayerVC
will receive twoList
, the original one saved in Realm and a copy of thisList
to power the UI. Let's call the listsfavoriteList
andcopyList
.So inside of the
didToggleStarButton
we would do something like this:
Code:
/// This view controller uses `AVFoundation` to play the videos from `FavoriteCollectionViewController`.
class PlayerViewControllerr: UIViewController {
/// A button to allow the user to favorite and unfavorite a `Video`
@IBOutlet weak var starButton: UIButton!
/// This is passed from `FavoriteCollectionViewController`
var favoriteList: FavoriteList!
/// A copy of the `FavoriteList` videos to power the UI.
var copiedList: List<Video>!
var currentIndexOfVideoInCopiedList: Int!
override func viewDidLoad() {
super viewDidLoad()
// Make a copy of the favoriteList to power the UI.
var copiedVideos = [Video]()
for video in favoriteList.videos {
let unmanagedVideo = Video(value: video)
copiedVideos.append(unmanagedVideo)
}
self.copiedList.append(copiedVideos)
}
// HELP: The app crashes here if I unfavorite a `Video`.
@IBAction func didToggleStarButton(_ sender: UIButton) {
// Do the unfavoriting and favoriting here.
// An example of unfavoriting:
let realm = try! Realm()
try! realm.write {
let videoToDeleteFromFavoriteList = favoriteList.videos[currentIndexOfVideoInCopiedList] /// Get the video that is currently playing
realm.delete(videoToDeleteFromOriginalList)
}
// Update star button to a new image depending on if the `Video` is favorited or not.
starButton.isSelected = //... update based on if the `Video` in the `FavoriteList` or not.
}
}
Any thoughts?
This one is definitely tricky for a number of architectural reasons.
You're right in that you could simply remove the object from the FavoriteList.videos
and then properly delete it from Realm when going to dismiss the controller, but you're right that if the user clicks the home button, or the app crashes before then, you'll end up with a headless video object. You would need to be able to make sure you can track that.
There's a couple of things you might be able to consider.
- Add an
isDeleted
property to theVideo
class. When the user unfavorites the video, remove theVideo
object fromFavoriteList.videos
, set that property totrue
, but leave it in Realm. Later on (either when the app quits or the view controller is dismissed), you can then do a general query for all objects whereisDeleted
istrue
and delete them then (This solves the headless problem). - Since your architecture requires a view controller relying on a model that could be deleted from under it, depending on how much information you're using from that
Video
object, it might be safer to make an unmanaged copy of theVideo
copy, and use that one to power the UI of the view controller. You can create a new copy of an existing Realm object by doinglet unmanagedVideo = Video(value: video)
.
Here is the workaround solution. Check if it works.
class PlayerViewControllerr: UIViewController {
var arrayForIndex = [Int]()
var videos = List<Video>()
@IBAction func didToggleStarButton(_ sender: UIButton) {
self.arrayForIndex.append(currentIndexInVideosList)
}
@overide func viewWillDisappear(_ animated : Bool){
super.viewWillDisappear(animated)
for i in arrayForIndex{
let realm = try! Realm()
try! realm.write {
let videoToDelete = videos[i] /// Get the video that is currently playing
realm.delete(videoToDelete)
}
}
}
来源:https://stackoverflow.com/questions/43081852/realm-cant-use-object-after-having-been-deleted