I get the following error when I try to load images from URL in async and render them in the main thread.The app is build with Xcode9-beta.
UIImageView.image must be used from main thread only
The images update only if I press on a cell in the tableView(and only for that particular button):
func updateUI(partyRock: PartyRock) {
videoTitle.text = partyRock.videoTitle
let url = URL(string: partyRock.imageURL)!
DispatchQueue.global().async {
do {
let data = try Data(contentsOf: url)
DispatchQueue.global().sync {
self.videoPreviewImage.image = UIImage(data: data)
}
} catch {
// handle error
}
}
}
Related
I am trying to extract all the frames from a video. Code is working fine for the video in Bundle Path, but it fails when i give a link of the video or I try to pick the video from File Manager.
Following are the issues that i have encountered:
AVAsset throws an error "Error Domn=AVFoundationErrorDomain Code=-11838 "Cannot initialize an instance of AVAssetReader with an asset at non-local URL".
When video is picked from Documents Directory, the fetched asset tracks are always 0.
Following is the code that i am using right now.
let asset = AVAsset(url: URL(string: path!)!)
self.playerController.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
if let path = path {
let asset:AVAsset = AVAsset(url: URL(string: path)!)
self.playerController.player = AVPlayer(playerItem: AVPlayerItem(asset: asset))
self.player.play()
asset.loadTracks(withMediaType: .video) { fetchedTracks, err in
do{
let reader = try AVAssetReader(asset: asset)
// read video frames as BGRA
let trackReaderOutput = AVAssetReaderTrackOutput(track: (fetchedTracks?.first)!, outputSettings:[String(kCVPixelBufferPixelFormatTypeKey): NSNumber(value: kCVPixelFormatType_32BGRA)])
reader.add(trackReaderOutput)
reader.startReading()
while let sampleBuffer = trackReaderOutput.copyNextSampleBuffer() {
print("sample at time \(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))")
if CMSampleBufferGetImageBuffer(sampleBuffer) != nil {
// process each CVPixelBufferRef here
guard let imageBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let ciimage = CIImage(cvPixelBuffer: imageBuffer)
self.frames.append(UIImage(ciImage: ciimage))
// see CVPixelBufferGetWidth, CVPixelBufferLockBaseAddress, CVPixelBufferGetBaseAddress, etc
}
}
self.collectionViewFrames.reloadData()
} catch let err {
print(err)
}
}
}
Greeting,
I'm currently facing a problem that my coroutine can't start. This is the first time I facing this issues and I can't find a proper solution online. Much appreciated if anyone can point me to the right direction to solve this issue.
Here are the code.
path_reference.GetDownloadUrlAsync().ContinueWith((Task<Uri> task) => {
if (!task.IsFaulted && !task.IsCanceled)
{
Debug.Log("Download URL: " + task.Result);
StartCoroutine(DownloadStuff(task.Result));
}
else
{
Debug.Log(task.Exception.ToString());
}
});
}
IEnumerator DownloadStuff(Uri uri)
{
Debug.Log("Start Download");
using (var www = UnityWebRequestTexture.GetTexture(uri))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
var texture = DownloadHandlerTexture.GetContent(www);
//Texture2D texture = new Texture2D(1, 1);
//if you need sprite for SpriteRenderer or Image
Sprite sprite = Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width,
texture.height), new Vector2(0.5f, 0.5f), 100.0f);
Debug.Log("Finished downloading!");
}
Debug.Log(www.downloadProgress);
}
}'
The task returned by Firebase probably finishes execution on a thread other than the main thread, and Unity coroutines can only run on the main thread.
Unity's support of multithreading and async is pretty spotty, including "eating" some errors if the continuations of those errors would execute on another thread other than the main thread.
To fix this, you need to change the function that starts your coroutine:
try {
// Important: ConfigureAwait(true) ensures the code after this will run in the
// same thread as the code before (which is the main thread!)
var url = await path_reference.GetDownloadUrlAsync().ConfigureAwait(true);
StartCoroutine(DownloadStuff(url));
} catch (Exception ex) {
// Tip: when logging errors, use LogException and pass the whole exception,
// that way you will get pretty links to the error line in the whole stack trace.
Debug.LogException(ex);
}
As an aside, I usually have a few extension methods on all my projects to deal with that while staying in async-world instead of coroutine-world (because at least with async I can catch errors and not just "halt and catch fire" like Unity's coroutines)
The main ones are:
public static Task ToTask(this YieldInstruction self, MonoBehaviour owner) {
var source = new TaskCompletionSource<object>();
IEnumerable Routine() {
yield return self;
source.SetResult(null);
}
return source.Task;
}
private static Task SendAsync(this UnityWebRequest self, MonoBehaviour owner) {
var source = new TaskCompletionSource<object>();
await self.SendWebRequest().ToTask(owner);
if (
self.isHttpError
|| self.isNetworkError
) {
source.SetException(new Exception(request.error));
yield break;
}
source.SetResult(null);
}
Which you can use like this, inside a MonoBehaviour:
await new WaitForSeconds(0.2f).ToTask(this);
UnityWebRequest request = /* etc */;
await request.SendAsync(this);
var texture = DownloadHandlerTexture.GetContent(request);
Note that these methods do not require ConfigureAwait, since their SetResult/SetException invocations are ran from Unity-provided coroutine continuations.
Coordinator pattern is an old topic with many libraries trying to solve it and I am learning it in simple example app.
My current set up is 3 rootViewControlers: LoadingStateCoordinator, WelcomeCoordinator, TabBarCoordinator but missing connection between UIKit and coordinators. I am trying to implement it with a UINavigationController but the button is not being called. I need a way to connect to back button and a reusable coordinator that I could push to and dellocate accordingly (that is without RxSwift).*Set up Welcome screen as the parent/main navigation and always be able to come back to it.**
So after user selects a form from modal view (vertical flow) presented I show on a push a TabBarCoordinator (horizontal). All viewControllers have empty.storyboard, UIViewController and Coordinator exept the TabBar.Here I only have a coordinator due to the set up of child tab coordinators and the magic needs to happen on a back button tap. Currenly this only being called when user comes from LoadingStateCoordinator. There I need to send the user back to the Welcome screen so they can change the onboarding set up. Here is the first code for LoadingStateCoordinator:
final class LoadingStateCoordinator: NSObject, Coordinator {
*// MARK: - Inputs required*
var childCoordinators: [Coordinator]
var presenter: UINavigationController
private let window: UIWindow
*// MARK: - Initialization*
init(window: UIWindow) {
self.window = window
childCoordinators = []
presenter = UINavigationController()
}
*// MARK: - Coordinator*
func start() {
let controller: LoadingStateViewController = LoadingStateViewController.instantiate()
window.rootViewController = controller
controller.delegate = self
}
}
*// MARK: - LoadingViewControllerDelegate*
extension LoadingStateCoordinator : LoadingViewControllerDelegate {
func performScreenSwitch() {
if UserDefaults.standard.userWasHere == false {
let tabCoordinator: TabBarCoordinator = TabBarCoordinator(window: window, tabBarController: UITabBarController())
window.rootViewController = presenter
addChildCoordinator(tabCoordinator)
tabCoordinator.start()
presenter.pushViewController(tabCoordinator.tabBarController!, animated: true)
} else {
let welcomeCoordinator = WelcomeCoordinator(window: window, presenter: presenter)
window.rootViewController = welcomeCoordinator.presenter
addChildCoordinator(welcomeCoordinator)
welcomeCoordinator.start()
}
}
}
And here is the TabBarCoordinator that need to perform back to Welcome screen action. When I present popToRootfunction it pushes the Welcome screen but all the button there are disbled. I guess to be retain cycle issue. Do I need funadametally another set up? Is there a way to popToRoot(vc) in this set up? What I tryed ended with runtime error "poping to non existing controller".
TabBarCoordinator code that need to perform this:
final class TabBarCoordinator: NSObject, Coordinator {
internal var presenter: UINavigationController
internal var tabBarController: UITabBarController?
internal var childCoordinators: [Coordinator]
var parentCoordinator: LoadingStateCoordinator?
lazy var leftBtn: UIBarButtonItem = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "arrow.turn.up.left"), for: .normal)
button.sizeToFit()
button.addTarget(self,
action: #selector(self.popToRoot(_:)),
for: .touchUpInside)
return UIBarButtonItem(customView: button)
}()
init(window: UIWindow, tabBarController: UITabBarController) {
self.tabBarController = tabBarController
childCoordinators = []
self.presenter = UINavigationController()
}
func start() {
performGetTabBar()
self.presenter.delegate = self
}
private func performGetTabBar() {
let coordinators: [Coordinator] = generateTabCoordinators()
coordinators.forEach({ coordinator in
coordinator.start()
addChildCoordinator(coordinator)
})
let presenters: [UIViewController] = coordinators.map({ coordinator -> UIViewController in
return coordinator.presenter
})
leftBtn.style = .plain
tabBarController?.navigationItem.leftBarButtonItem = leftBtn
tabBarController?.setViewControllers(presenters, animated: false)
selectTab(type: SurfTripCoordinator.self)
}
private func generateTabCoordinators() -> [Coordinator] {
let calculatorCoordinator: CalculatorCoordinator = CalculatorCoordinator(presenter: UINavigationController())
let tripCoordinator: SurfTripCoordinator = SurfTripCoordinator(presenter: UINavigationController())
let sellCoordinator: SavedTripsCoordinator = SavedTripsCoordinator(presenter: UINavigationController())
return [calculatorCoordinator, tripCoordinator, sellCoordinator]
}
*//this is not being called when coming from vertical flow*
#objc func popToRoot(_ sender: UIBarButtonItem) {
let storyboard: UIStoryboard = UIStoryboard(name: Constants.Storyboards.welcomeViewCoordinator, bundle: nil)
let controller: WelcomeViewController = WelcomeViewController.instantiate(from: storyboard)
tabBarController?.navigationController?.pushViewController(controller, animated: true)
}
}
extension TabBarCoordinator: UINavigationControllerDelegate {
func selectTab<T: Coordinator>(type _: T.Type) {
guard let index = childCoordinators.firstIndex(where: { coordinator in
coordinator is T
}) else {
return
}
tabBarController?.selectedIndex = index
}
}
and here is the current WelcomeCoordinator set up
class WelcomeCoordinator: NSObject, Coordinator {
internal var presenter: UINavigationController
var childCoordinators: [Coordinator]
init(window: UIWindow, presenter: UINavigationController) {
self.presenter = presenter
childCoordinators = []
}
func start() {
let storyboard: UIStoryboard = UIStoryboard(name: Constants.Storyboards.welcomeViewCoordinator, bundle: nil)
let controller: WelcomeViewController = WelcomeViewController.instantiate(from: storyboard)
controller.delegate = self
presenter.pushViewController(controller, animated: true)
}
}
extension WelcomeCoordinator : WelcomeViewControllerDelegate {
func performAddLevel() {
let addLevelCoordinator: AddLevelViewCoordinator = AddLevelViewCoordinator(presenter: UINavigationController())
addLevelCoordinator.start()
addChildCoordinator(addLevelCoordinator)
addLevelCoordinator.presenter.modalPresentationStyle = .fullScreen
presenter.present(addLevelCoordinator.presenter, animated: true, completion: nil)
}
}
sorry for the long post I wish there was more reaktive native way to do this...
Ok so I found partlly a solution the back button solution for my case: not using pushViewController or show because it comes with back button. presenter.setViewControllers([tabCoordinator.tabBarController!], animated: true) and there setting the navBar to hidden. I made my own navItem button to navigate to rootVC. Next step to allocate and remove all child tabBar coordinators on back tap recognized.
I'm populating a List with a Realm Result set.
When navigating from this list it opens a new view then automatically closes that view.
Using a struct presents no issue.
Why would the second view automatically close?
I have a screen recording but cant post here.
import SwiftUI
import Combine
struct TestStruct:Identifiable{
let id = UUID()
let firstname: String
}
extension TestStruct {
static func all() -> [TestStruct]{
return[
TestStruct(firstname: "Joe"),
TestStruct(firstname: "Jane"),
TestStruct(firstname: "Johns")
]
}
}
struct TestListView: View {
let realmList = Horoscope.getHoroscopes() //Fetches from Realm
let structList = TestStruct.all()
var body: some View {
NavigationView{
// This owrks
// List(structList) { item in
// MyItemRow(itemTxt: item.firstname)
// }
//This automatically closes the view
List(realmList) { item in
MyItemRow(itemTxt: item.firstname)
}
.navigationBarTitle("Charts", displayMode: .automatic)
.navigationBarItems(trailing: EditButton())
}
}
}
struct MyItemRow: View {
var itemTxt:String
var body: some View {
NavigationLink(destination: Text("Test")) {
Text(itemTxt)
}
}
}
struct TestListView_Previews: PreviewProvider {
static var previews: some View {
TestListView()
}
}
I think the answer can be found here
In short, do not generate the id of the collection on which the ForEach iterates. It would detect a change and navigate back.
Realm object has an auto generated id property with each reference, try replacing it with a consistent id
The following solution worked for me.
The code with an issue (specifying id: \.self is the root cause since it uses the hash calculated from all objects the Stream object consists of, including the data that lies in a subarray).
...
List(streams, id: \.self) { stream in
...
The code with no issues:
...
List(streams, id: \._id) { stream in
// or even List(streams) { stream in
...
The streams is a #ObservedResults(Stream.self) var streams and the object scheme is:
final class Stream: Object, ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: ObjectId
#Persisted var title: String
#Persisted var subtitle: String?
#Persisted var topics = RealmSwift.List<Topic>()
// tags, etc.
}
The issue happened when I added new topic at the topics list in the first stack of the navigationView.
I cannot access images inside an asset catalog after it's been downloaded through NSBundleResourceRequest (on demand resource).
My code, say the image set name is "snow_4" with on demand resource tag "tag1"
NSBundleResourceRequest* resourceRequest = [[NSBundleResourceRequest alloc] initWithTags:[NSSet setWithObjects:#"tag1", nil]];
[resourceRequest conditionallyBeginAccessingResourcesWithCompletionHandler:
^(BOOL resourcesAvailable){
// if resource is not available download it
if (!resourcesAvailable) {
[resourceRequest beginAccessingResourcesWithCompletionHandler:
^(NSError * __nullable error){
if(!error){
UIImage* image = [UIImage imageNamed:#"snow_4"]; // image is null
}
}];
}else{
UIImage* image = [UIImage imageNamed:#"snow_4"]; // image is null
}
}];
Below is my Disk Report, please note that [UIImage imageNamed:#"snow_3"] (marked as 'In Use') returns a correct object but not images that are marked as 'Downloaded'
Appreciating your time and help!
Thanks,
Mars
Make sure NSBundleResourceRequest is not released early. Try to let NSBundleResourceRequest variable globally,
and access the image with UIImage(named: "xxx", in: self.bundleReq.bundle, compatibleWith: nil)
do this
image = [UIImage imageNamaed:#"snow_4" inBundle:[resourceRequest bundle]];
let tags = NSSet(array: ["tag1","tag2"])
let resourceRequest = NSBundleResourceRequest(tags: tags as! Set<String>)
resourceRequest.conditionallyBeginAccessingResourcesWithCompletionHandler {(resourcesAvailable: Bool) -> Void in
if resourcesAvailable {
// Do something with the resources
} else {
resourceRequest.beginAccessingResourcesWithCompletionHandler {(err: NSError?) -> Void in
if let error = err {
print("Error: \(error)")
} else {
// Do something with the resources
}
}
}
}