I have a camera app that displays thumbnails of the user's photo library in a UICollectionView layered on top of the live camera feed UIView.
I was hoping that I could easily 'refresh' the contents of the UICollectionView every time the user takes a new photo using the reloadData() property:
#IBAction func snapNewPhoto(_ sender: Any) {
takePhotoMethod()
myCollectionView.reloadData()
myCollectionView.collectionViewLayout.invalidateLayout()
}
and this is my takePhoto method:
func takePhotoMethod () {
if let videoConnection = sessionOutput.connection(with: AVMediaType.video) {
sessionOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {
buffer, error in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer!)
self.imageArray.append(UIImage(data: imageData!)!)
print ("Photo taken", self.imageArray.count)
})
}
}
This does not work, I guess the solution is a bit more complex, do I have to 'reload' the entire View Controller to reload the UICollectionView or is there another more eloquent approach?
func takePhotoMethod () {
if let videoConnection = sessionOutput.connection(with: AVMediaType.video) {
sessionOutput.captureStillImageAsynchronously(from: videoConnection, completionHandler: {
buffer, error in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer!)
self.imageArray.append(UIImage(data: imageData!)!)
myCollectionView.reloadData()//reload must be done in here.
print ("Photo taken", self.imageArray.count)
})
}
}
Related
My current setup: TabViewController that is connected to two TableViewControllers embedded in navigation controllers. I am able to update the tableview no problem, on app load and when switching back between the different tabs.
My problem comes when I open another tableviewcontroller to edit. When I hit save from this view controller, it updates the data and saves everything just fine however, My tableView will not update no matter what I try unless I switch between the different tabs or close and reopen the app.
I have tried using delegates to trigger the update, I have tried use NSNotificationCenter, and no dice.
Has anybody else had this issue?
Here is my save function:
func saveDose(completion: (_ finished: Bool) -> ()) {
if searchName == "" {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let dose = Dose(context: managedContext)
let doseNumberString = numberDosesText.text
let doseNumberInt = Int64(doseNumberString!) ?? 0
dose.name = nameText.text
dose.script = scriptText.text
dose.dosage = dosageText.text
// dose.doseInterval = doseInterval
dose.firstDose = datePicker.date
dose.numberDoses = doseNumberInt
dose.doseReminder = remindersSwitch.isOn
do {
try managedContext.save()
print("Data Saved")
completion(true)
} catch {
print("Failed to save data: ", error.localizedDescription)
completion(false)
}
} else {
update(name:searchName, firstDose: searchDate)
completion(true)
}
}
And here is where I call it and load back to my other tableview.
#IBAction func saveBtnPressed(_ sender: UIBarButtonItem) {
saveDose { (done) in
if done {
print("We need to return now")
navigationController?.popViewController(animated: true)
self.dismiss(animated: true, completion: nil)
} else {
print("Try again")
}
}
}
And here is where I reload my tableview when the view appears
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("View has been loaded back from other view")
self.fetchData()
self.tableView.reloadData()
print("View will appear")
}
And here is my fetchData and loadData functions
func fetchData() {
loadData { (done) in
if done {
setEmptyView(Array: logArray.count)
}
}
}
func loadData(completion: (_ complete: Bool) -> ()) {
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Log")
do {
logArray = try managedContext.fetch(request) as! [Log]
print("Data Fetched No Issues")
completion(true)
} catch {
print("Unable to fetch data: ", error.localizedDescription)
completion(false)
}
}
Again I have tried delegates and have used this code in other applications that have worked fine. Is it something to do with the tab bar controller?
The data is obviously saving fine, I just can't get the tableView to update properly.
It seems like it is not calling the viewWillAppear function after the save. I have tried using delegates as well to force the update of the tableview but nothing has been working.
Basically you need to call tableView.reloadData(). after you have done saving.
Moreover you can use beginUpdates, endUpdates on tableView to perform animations on specific rows. for more information on this you may refer to This Link
Reload your tableview in custom delegates method your problem will be solved.
i need your help with GCD in swift, i just started using this tool and i'm little confused. So let's say i have a function foo() that taking photo and saving it to var, inside this function there is another function that checking if there are faces on that photo and returning true or false. Next, when user taps usePhotoFromPreview button it will save photo through custom delegate whenever there aren't any faces on that photo like this:
var image: UIImage?
var checkingPhotoForFaces: Bool?
func foo(){
let videoConnection = imageOutput?.connection(withMediaType: AVMediaTypeVideo)
imageOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: { (imageDataSampleBuffer, error) -> Void in
if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
image = UIImage(data: imageData)
self.checkingPhotoForFaces = self.detect(image: image)
}
})
}
func detect(image: UIImage) -> Bool{
.....
}
func usePhotoFromPreview(){
self.dismiss(animated: true, completion: {
if self.checkingPhotoForFaces == false{
self.delegate?.takenPhoto(photo: self.image!)
print("photo taken")
}else{
self.delegate?.takenPhoto(photo: nil)
print("no photo taken")
}
})
}
So now the tricky part, i'd like to make detect function to be executed asynchronously and only when it's done execute usePhotoFromPreview. detect func i suppose should be on main thread because of CoreImage implementation. I simply made something like this:
func foo(){
...
DispatchQueue.global().async {
self.checkingPhotoForFaces = self.detect(image: stillImage)
}
...
}
but the problem is when user taps too quick on that button it won't work, cuz detect func is still processing, so i think i need some kind of queue but i'm confused which one.
btw. please, do not cling to the lack of some parameters above, i'm writing it on the fly and that's not the point
Thank you for your help
Change detect as commented above:
func detect(image: UIImage, completion: ()->(detectedFaces: Bool)){
//.....
completion(true|false)
}
Then
if let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer) {
image = UIImage(data: imageData)
self.detect(image) { detectedFaces in
self.checkingPhotoForFaces = detectedFaces
//Enable button?
}
}
Is there any way to record IOS screen programmatically. Means whatever activity you are doing like clicking buttons, Scrolling tableviews.
Even if a video is playing that will be captured again along with some other activity?
Have tried these
https://www.raywenderlich.com/30200/avfoundation-tutorial-adding-overlays-and-animations-to-videos
https://github.com/alskipp/ASScreenRecorder
but with these libraries won't provide quality video. I need quality video.
The issue is that with video playing in the background when i capture screen it does not show smooth video. It shows like one frame of video and then after 3-4 secs 2nd frame and so on. Also quality of video is not good its blurred
As of iOS 9, it looks like ReplayKit is available to greatly simplify this.
https://developer.apple.com/reference/replaykit
https://code.tutsplus.com/tutorials/ios-9-an-introduction-to-replaykit--cms-25458
Update: This may be less relevant now that iOS 11 has a built-in screen recorder, but the following Swift 3 code worked for me:
import ReplayKit
#IBAction func toggleRecording(_ sender: UIBarButtonItem) {
let r = RPScreenRecorder.shared()
guard r.isAvailable else {
print("ReplayKit unavailable")
return
}
if r.isRecording {
self.stopRecording(sender, r)
}
else {
self.startRecording(sender, r)
}
}
func startRecording(_ sender: UIBarButtonItem, _ r: RPScreenRecorder) {
r.startRecording(handler: { (error: Error?) -> Void in
if error == nil { // Recording has started
sender.title = "Stop"
} else {
// Handle error
print(error?.localizedDescription ?? "Unknown error")
}
})
}
func stopRecording(_ sender: UIBarButtonItem, _ r: RPScreenRecorder) {
r.stopRecording( handler: { previewViewController, error in
sender.title = "Record"
if let pvc = previewViewController {
if UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad {
pvc.modalPresentationStyle = UIModalPresentationStyle.popover
pvc.popoverPresentationController?.sourceRect = CGRect.zero
pvc.popoverPresentationController?.sourceView = self.view
}
pvc.previewControllerDelegate = self
self.present(pvc, animated: true, completion: nil)
}
else if let error = error {
print(error.localizedDescription)
}
})
}
// MARK: RPPreviewViewControllerDelegate
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
previewController.dismiss(animated: true, completion: nil)
}
ReplayKit is available, although you are not allowed to access the result video, the only way I've found so far is to make a number of screenshots (store them in array of images) and then convert these images to the video, not very efficient from performance standpoint though, but might work when you don't really need a 30/60 fps screen recording and might be ok w/ 6-20 pfs. Here's the full example.
Check out ScreenCaptureView, this has video-recording support built-in (see link).
What this does is it saves the contents of a UIView to a UIImage. The author suggests you can save a video of the app in use by passing the frames through AVCaptureSession.
I believe it hasn't been tested with an OpenGL subview, but assuming that it works you might be able to modify it slightly to include audio and then you'd be set.
AVCaptureSession Sample
AVCaptureSession Reference
import UIKit
import AVFoundation
class ViewController: UIViewController {
let captureSession = AVCaptureSession()
let stillImageOutput = AVCaptureStillImageOutput()
var error: NSError?
override func viewDidLoad() {
super.viewDidLoad()
let devices = AVCaptureDevice.devices().filter{ $0.hasMediaType(AVMediaTypeVideo) && $0.position == AVCaptureDevicePosition.Back }
if let captureDevice = devices.first as? AVCaptureDevice {
captureSession.addInput(AVCaptureDeviceInput(device: captureDevice, error: &error))
captureSession.sessionPreset = AVCaptureSessionPresetPhoto
captureSession.startRunning()
stillImageOutput.outputSettings = [AVVideoCodecKey:AVVideoCodecJPEG]
if captureSession.canAddOutput(stillImageOutput) {
captureSession.addOutput(stillImageOutput)
}
if let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) {
previewLayer.bounds = view.bounds
previewLayer.position = CGPointMake(view.bounds.midX, view.bounds.midY)
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
let cameraPreview = UIView(frame: CGRectMake(0.0, 0.0, view.bounds.size.width, view.bounds.size.height))
cameraPreview.layer.addSublayer(previewLayer)
cameraPreview.addGestureRecognizer(UITapGestureRecognizer(target: self, action:"saveToCamera:"))
view.addSubview(cameraPreview)
}
}
}
func saveToCamera(sender: UITapGestureRecognizer) {
if let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo) {
stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) {
(imageDataSampleBuffer, error) -> Void in
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)
UIImageWriteToSavedPhotosAlbum(UIImage(data: imageData), nil, nil, nil)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
You can use this library to record a view : screen-cap-view available on GitHub written in Objective C.
**And to use it in swift:**
--> Drag and drop the .m and .h files in your xcode project.
--> Make a header file and import the this file in that : *#import "IAScreenCaptureView.h"*
--> Then give a View this class from the PropertyInspector and then make a IBOutlet for that view . Something like this:
*#IBOutlet weak var contentView: IAScreenCaptureView!*
--> Then Finally just simply start and stop the recording of the view where ever and when ever you want and for that the code will be like this :
For Starting the Recording : *contentView.startRecording()*
For Stoping the Recording : *contentView.stopRecording()*
//Hope this helps.Happy coding. \o/ , ¯\_(ツ)_/¯ ,(╯°□°)╯︵ ┻━┻
I'm displaying a photo in my app and I'm using UIImage for that. So far this is how I'm doing that:
func getDataFromUrl(url:NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}
func downloadImage(url: NSURL){
getDataFromUrl(url) { (data, response, error) in
dispatch_async(dispatch_get_main_queue()) { () -> Void in
guard let data = data where error == nil else { return }
print("Download Finished")
self.requestPhoto.image = UIImage(data: data)
}
}
}
and then in viewDidLoad() I'm doing:
if let checkedUrl = NSURL(string: photo) {
requestPhoto.contentMode = .ScaleAspectFit
downloadImage(checkedUrl)
}
That leaves the UIImage filled with my photo, but it's not clickable and it's the size of the original component. Is there a way of adding some kind of listener or something that will display the photo on fullscreen when user taps the UIImage component?
What you need is to add a UITapGestureRecognizer to your requestPhoto image view. Something like this :
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapHandler"))
self.requestPhoto.addGestureRecognizer(tapGestureRecognizer)
self.requestPhoto.userInteractionEnabled = true
The last line is needed as UIImageView has it turned of by default, so the touches get canceled.
And then in the same class :
func tapHandler(sender: UITapGestureRecognizer) {
if sender.state == .Ended {
// change the size of the image view so that it fills the whole screen
}
}
The whole thing could also benefit from a flag saying if the view is fullscreen or not, so that when the user taps the fullscreen image you can reverse the process.
As always, you can read more in the docs.
How to enlarge thumbnail photo into fullscreen photo view on iOS?
I resolved this problem by creating new UIImageView object with the same image in it and set the frame of it equal to thumbnail image:
let fullscreenPhoto = UIImageView(frame: thumbnailImage.frame)
Then add image as sublayer into main window:
UIApplication.sharedApplication().windows.first!.addSubview(fullscreenPhoto)
Resizing is made with animation using block:
let windowFrame = UIApplication.sharedApplication().windows.first!.frame
UIView.animateWithDuration(0.4, delay: 0.0, options: .CurveEaseInOut, animations: {
fullscreenPhoto.frame = windowFrame
fullscreenPhoto.alpha = 1
fullscreenPhoto.layoutSubviews()
}, completion: { _ in
})
You can find ready solution here: https://github.com/lukszar/SLImageView
There is provided gesture recognizers for show and hide fullscreen.
You just need to set your UIImageView to SLImageView class in Storyboard and all magic is done.
I have a header view for every UITableViewCell. In this header view, I load a picture of an individual via an asynchronous function in the Facebook API. However, because the function is asynchronous, I believe the function is called multiple times over and over again, causing the image to flicker constantly. I would imagine a fix to this issue would be to load the images in viewDidLoad in an array first, then display the array contents in the header view of the UITableViewCell. However, I am having trouble implementing this because of the asynchronous nature of the function: I can't seem to grab every photo, and then continue on with my program. Here is my attempt:
//Function to get a user's profile picture
func getProfilePicture(completion: (result: Bool, image: UIImage?) -> Void){
// Get user profile pic
let url = NSURL(string: "https://graph.facebook.com/1234567890/picture?type=large")
let urlRequest = NSURLRequest(URL: url!)
//Asynchronous request to display image
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
if error != nil{
println("Error: \(error)")
}
// Display the image
let image = UIImage(data: data)
if(image != nil){
completion(result: true, image: image)
}
}
}
override func viewDidLoad() {
self.getProfilePicture { (result, image) -> Void in
if(result == true){
println("Loading Photo")
self.creatorImages.append(image!)
}
else{
println("False")
}
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Show section header cell with image
var cellIdentifier = "SectionHeaderCell"
var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell
headerView.headerImage.image = self.creatorImages[section]
headerView.headerImage.clipsToBounds = true
headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
return headerView
}
As seen by the program above, I the global array that I created called self.creatorImages which holds the array of images I grab from the Facebook API is always empty and I need to "wait" for all the pictures to populate the array before actually using it. I'm not sure how to accomplish this because I did try a completion handler in my getProfilePicture function but that didn't seem to help and that is one way I have learned to deal with asynchronous functions. Any other ideas? Thanks!
I had the same problem but mine was in Objective-C
Well, the structure is not that different, what i did was adding condition with:
headerView.headerImage.image
Here's an improved solution that i think suits your implementation..
since you placed self.getProfilePicture inside viewDidLoad it will only be called once section==0 will only contain an image,
the code below will request for addition image if self.creatorImages's index is out of range/bounds
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
//Show section header cell with image
var cellIdentifier = "SectionHeaderCell"
var headerView = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! SectionHeaderCell
if (section < self.creatorImages.count) // validate self.creatorImages index to prevent 'Array index out of range' error
{
if (headerView.headerImage.image == nil) // prevents the blinks
{
headerView.headerImage.image = self.creatorImages[section];
}
}
else // requests for additional image at section
{
// this will be called more than expected because of tableView.reloadData()
println("Loading Photo")
self.getProfilePicture { (result, image) -> Void in
if(result == true) {
//simply appending will do the work but i suggest something like:
if (self.creatorImages.count <= section)
{
self.creatorImages.append(image!)
tableView.reloadData()
println("self.creatorImages.count \(self.creatorImages.count)")
}
//that will prevent appending excessively to data source
}
else{
println("Error loading image")
}
}
}
headerView.headerImage.clipsToBounds = true
headerView.headerImage.layer.cornerRadius = headerView.headerImage.frame.size.width / 2
return headerView
}
You sure have different implementation from what i have in mind, but codes in edit history is not in vain, right?.. hahahaha.. ;)
Hope i've helped you.. Cheers!