How do i stop detecting an object in swift? - ios

I'm currently working IOS mobile project where objects are detected in a frame and then translated to speech to aid the visually impaired. My application already detects objects in a frame, but once it does so it doesn't stop detecting. When I'm trying to convert the object name to speech it keeps iterating over the same name over and over again.
For clarification, when I point my camera at a "chair" it gives over a 100 log for chairs in which the text to speech has to say those 100 "chairs" before moving on to the next object.
This is my viewController code:
import UIKit
import Vision
import CoreMedia
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var videoPreview: UIView!
#IBOutlet weak var boxesView: DrawingBoundingBoxView!
#IBOutlet weak var labelsTableView: UITableView!
#IBOutlet weak var inferenceLabel: UILabel!
#IBOutlet weak var etimeLabel: UILabel!
#IBOutlet weak var fpsLabel: UILabel!
let objectDectectionModel = MobileNetV2_SSDLite()
// MARK: - Vision Properties
var request: VNCoreMLRequest?
var visionModel: VNCoreMLModel?
var isInferencing = false
// MARK: - AV Property
var videoCapture: VideoCapture!
let semaphore = DispatchSemaphore(value: 1)
var lastExecution = Date()
// MARK: - TableView Data
var predictions: [VNRecognizedObjectObservation] = []
// MARK - Performance Measurement Property
private let measure = Measure()
let maf1 = MovingAverageFilter()
let maf2 = MovingAverageFilter()
let maf3 = MovingAverageFilter()
// MARK: - View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// setup the model
setUpModel()
// setup camera
setUpCamera()
// setup delegate for performance measurement
measure.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.videoCapture.start()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.videoCapture.stop()
}
// MARK: - Setup Core ML
func setUpModel() {
if let visionModel = try? VNCoreMLModel(for: objectDectectionModel.model) {
self.visionModel = visionModel
request = VNCoreMLRequest(model: visionModel, completionHandler: visionRequestDidComplete)
request?.imageCropAndScaleOption = .scaleFill
} else {
fatalError("fail to create vision model")
}
}
// MARK: - SetUp Video
func setUpCamera() {
videoCapture = VideoCapture()
videoCapture.delegate = self
videoCapture.fps = 30
videoCapture.setUp(sessionPreset: .vga640x480) { success in
if success {
// add preview view on the layer
if let previewLayer = self.videoCapture.previewLayer {
self.videoPreview.layer.addSublayer(previewLayer)
self.resizePreviewLayer()
}
// start video preview when setup is done
self.videoCapture.start()
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
resizePreviewLayer()
}
func resizePreviewLayer() {
videoCapture.previewLayer?.frame = videoPreview.bounds
}
}
// MARK: - VideoCaptureDelegate
extension ViewController: VideoCaptureDelegate {
func videoCapture(_ capture: VideoCapture, didCaptureVideoFrame pixelBuffer: CVPixelBuffer?, timestamp: CMTime) {
// the captured image from camera is contained on pixelBuffer
if !self.isInferencing, let pixelBuffer = pixelBuffer {
self.isInferencing = true
// start of measure
self.measure.start()
// predict!
self.predictUsingVision(pixelBuffer: pixelBuffer)
}
}
}
extension ViewController {
func predictUsingVision(pixelBuffer: CVPixelBuffer) {
guard let request = request else { fatalError() }
// vision framework configures the input size of image following our model's input configuration automatically
self.semaphore.wait()
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try? handler.perform([request])
}
// MARK: - Post-processing
func visionRequestDidComplete(request: VNRequest, error: Error?) {
self.measure.labell(with: "endInference")
if let predictions = request.results as? [VNRecognizedObjectObservation] {
// print(predictions.first?.labels.first?.identifier ?? "nil")
// print(predictions.first?.labels.first?.confidence ?? -1)
let pred = request.results?.first
// print(pred)
// print(predictions.first?.labels.first?.identifier as Any)
// print(predictions)
self.predictions = predictions
DispatchQueue.main.async {
self.boxesView.predictedObjects = predictions
self.labelsTableView.reloadData()
// end of measure
self.measure.end()
self.isInferencing = false
}
} else {
// end of measure
self.measure.end()
self.isInferencing = false
}
self.semaphore.signal()
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return predictions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "InfoCell") else {
return UITableViewCell()
}
// Getting the detected object and translating them to speech.
// This is where i face the problem of translating the objects as the objects
// keep iterating over themsleves.
let result = predictions[indexPath.row].label ?? "N/A"
// when trying to print(result) i get all the labells detected but it does not stop.
let utterance = AVSpeechUtterance(string: result)
utterance.voice = AVSpeechSynthesisVoice(language: "en-GB")
utterance.rate = 0.5
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
let rectString = predictions[indexPath.row].boundingBox.toString(digit: 2)
let confidence = predictions[indexPath.row].labels.first?.confidence ?? -1
let confidenceString = String(format: "%.3f", confidence/*Math.sigmoid(confidence)*/)
cell.textLabel?.text = predictions[indexPath.row].label ?? "N/A"
cell.detailTextLabel?.text = "\(rectString), \(confidenceString)"
return cell
}
}
// MARK: - 📏(Performance Measurement) Delegate
extension ViewController: MeasureDelegate {
func updateMeasure(inferenceTime: Double, executionTime: Double, fps: Int) {
//print(executionTime, fps)
DispatchQueue.main.async {
self.maf1.append(element: Int(inferenceTime*1000.0))
self.maf2.append(element: Int(executionTime*1000.0))
self.maf3.append(element: fps)
self.inferenceLabel.text = "inference: \(self.maf1.averageValue) ms"
self.etimeLabel.text = "execution: \(self.maf2.averageValue) ms"
self.fpsLabel.text = "fps: \(self.maf3.averageValue)"
}
}
}
class MovingAverageFilter {
private var arr: [Int] = []
private let maxCount = 10
public func append(element: Int) {
arr.append(element)
if arr.count > maxCount {
arr.removeFirst()
}
}
public var averageValue: Int {
guard !arr.isEmpty else { return 0 }
let sum = arr.reduce(0) { $0 + $1 }
return Int(Double(sum) / Double(arr.count))
}
}

It seems you call tableView.reloadData() in every frame, because visionRequestDidComplete is called each frame. Thus, cellForRowAtIndexPath (and therein AVSpeechSynthesizer ) gets called over and over which produces the sound.
You should re-evaluate if you need to update your tableview that often. Maybe you only need to update the tableview, if there are new observations? You could check for that using the predictions array in visionRequestDidComplete.
You might also wanna use Apple's own VoiceOver system to read out UI elements. That's the standard approach to add support for visually impaired users. This would also offer the benefit that the user can navigate within the tableview and the text of each cell will be read out accordingly.

Related

Programmatically set the row height for a tableview controller with varying image aspect ratios?

Currently I'm working on a media feed that may contain images in varying aspect ratios. Each image is to be scaled to the same width on the screen and the height will be adjusted according to the same ratio.
My tableViewController is using automatic dimensions. Each image is scaled to the correct width and height then a new image is created which is fed into its corresponding tableViewCell.
However, when the width and height is set programmatically (via auto dimension and the scaled image), I find a strange bug where I have to scroll around to get the image to display. (Video below). It's not until I use heightForRowAt and use a constant value where the aspect ratio is not preserved that the images display at the apps launch.
I'd love to be able to display these images in their correct aspect ratio. Any help is definitely appreciated.
Thanks!
First Image: auto dimension
Images display after scrolling action (maybe a bug)
Second Image: height for row at
TableVC cell class
import UIKit
import Firebase
class UserPostCell: UITableViewCell {
// MARK: - Outlets
#IBOutlet private weak var userProfileBtn: UIButton!
#IBOutlet private weak var likeBtn: UIButton!
#IBOutlet private weak var captionTxtField: UITextField!
#IBOutlet weak var postImage: UIImageView!
private(set) var height: CGFloat?
override func awakeFromNib() {
super.awakeFromNib()
}
func configureCell(post: UserPost) {
let gsReference = Storage.storage().reference(forURL: post.photoURL)
var image: UIImage?
gsReference.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
debugPrint("Error: \(error.localizedDescription)")
} else {
image = UIImage(data: data!)
//let h = image!.size.height
let w = image!.size.width
let wRatio = self.frame.size.width / w
//create new image at correct scale
let newImage = UIImage(data: data!, scale: 1 / wRatio)
self.postImage.frame.size.height = newImage!.size.height
self.postImage.image = newImage
self.userProfileBtn.setTitle(post.username, for: .normal)
self.captionTxtField.text = post.caption
}
}
}
}
View Controller
import UIKit
import Firebase
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Outlets
#IBOutlet private weak var tableView: UITableView!
// MARK: - Variables
private var userPosts = [UserPost]()
private var postsCollectionRef: CollectionReference!
private var usersCollectionRef: CollectionReference!
private var handle: AuthStateDidChangeListenerHandle?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 450
postsCollectionRef = Firestore.firestore().collection(POSTS_REF)
usersCollectionRef = Firestore.firestore().collection(USERS_REF)
loadPosts()
}
func loadPosts() {
var username: String?
var profileURL: String?
//var photoURL: String?
var uid: String?
//retrieve posts from database
postsCollectionRef.getDocuments { snapshot, error in
if let err = error {
debugPrint(err.localizedDescription)
} else {
guard let snap = snapshot else { return }
for d in snap.documents {
let data = d.data()
uid = data[USER_ID] as? String ?? ""
//retrieve info about author of each post
let userDocRef = self.usersCollectionRef.document(uid!)
//retrieve user info for each post
userDocRef.getDocument { document, error in
if let document = document, document.exists {
let dat = document.data()
//user data (dat)
username = dat![USERNAME] as? String ?? "Anonymous"
profileURL = dat![PROFILE_IMAGE] as? String ?? ""
// dat vs data fix this ^^^
//post data (data)
let photoURL = data[PHOTO_URL] as? String ?? ""
let caption = data[CAPTION] as? String ?? ""
let numComments = data[NUM_COMMENTS] as? Int ?? 0
let numLikes = data[NUM_LIKES] as? Int ?? 0
let timestamp = data[TIME_STAMP] as? Date ?? Date()
let documentId = d.documentID
print("photolink: \(photoURL)")
print("caption: \(caption)")
let newPost = UserPost(name: username!, timestamp: timestamp, caption: caption, numLikes: numLikes, numComments: numComments, documentId: documentId, UID: uid!, profile: profileURL!, photo: photoURL)
self.userPosts.append(newPost)
self.tableView.reloadData()
} else {
print("This user document does not exist")
}
}
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
//check if the user is logged in or not
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if user == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(identifier: "loginVC")
self.present(loginVC, animated: true, completion: nil)
} else {
//self.loadPosts()
// set listener
self.tableView.reloadData()
}
})
}
#IBAction func logoutBtnTapped(_ sender: Any) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
} catch let signoutError as NSError {
debugPrint("Error signing out: \(signoutError)")
}
}
#IBAction func reload(_ sender: Any) {
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "userPostCell", for: indexPath) as? UserPostCell{
cell.configureCell(post: userPosts[indexPath.row])
return cell
} else {
return UITableViewCell()
}
}
}
The reason of this your cell is already created before UIImage loads up.
Best way to prevent this, give one of the constraints of the imageView.

Only one switch is on

I'm struggle with following challenge. I created table view with custom cell that contains switch. I wanna only one switch can be on i.e, for instance after launch I switched on 3rd switched and then I switched on 7th switch and thus the 3rd one is switched off and so on. I use rx + protocols for cell and don't understand all the way how to determine which switch was toggled. Previously I was going to use filter or map to look up in dataSource array which switch is on and somehow handle this, but now I messed up with it. I'm not sure it's possible without using table view delegate methods. Thanks a lot, hope someone could explain where I am wrong.
//My cell looks like this:
// CellViewModel implementation
import Foundation
import RxSwift
protocol ViewModelProtocol {
var bag:DisposeBag {get set}
func dispose()
}
class ViewModel:ViewModelProtocol {
var bag = DisposeBag()
func dispose() {
self.bag = DisposeBag()
}
}
protocol CellViewModelProtocol:ViewModelProtocol {
var isSwitchOn:BehaviorSubject<Bool> {get set}
}
class CellVM:ViewModel, CellViewModelProtocol {
var isSwitchOn: BehaviorSubject<BooleanLiteralType> = BehaviorSubject(value: false)
let internalBag = DisposeBag()
override init() {
}
}
//My Cell implementation
import UIKit
import RxSwift
import RxCocoa
class Cell:UITableViewCell {
static let identifier = "cell"
#IBOutlet weak var stateSwitch:UISwitch!
var vm:CellViewModelProtocol? {
didSet {
oldValue?.dispose()
self.bindUI()
}
}
var currentTag:Int?
var bag = DisposeBag()
override func awakeFromNib() {
super.awakeFromNib()
self.bindUI()
}
override func prepareForReuse() {
super.prepareForReuse()
self.bag = DisposeBag()
}
private func bindUI() {
guard let vm = self.vm else { return }
self.stateSwitch.rx.controlEvent(.valueChanged).withLatestFrom(self.stateSwitch.rx.value).observeOn(MainScheduler.asyncInstance).bind(to: vm.isSwitchOn).disposed(by: vm.bag)
}
}
//TableViewController implementation
import UIKit
import RxSwift
import RxCocoa
class TableViewController: UITableViewController {
private var dataSource:[CellViewModelProtocol] = []
var vm = TableViewControllerVM()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 70
self.tableView.rowHeight = UITableView.automaticDimension
self.bindUI()
}
private func bindUI() {
vm.dataSource.observeOn(MainScheduler.asyncInstance).bind { [weak self] (dataSource) in
self?.dataSource = dataSource
self?.tableView.reloadData()
}.disposed(by: vm.bag)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSource.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cell.identifier, for: indexPath) as! Cell
if cell.vm == nil {
cell.vm = CellVM()
}
return cell
}
}
class TableViewControllerVM:ViewModel {
var dataSource:BehaviorSubject<[CellViewModelProtocol]> = BehaviorSubject(value: [])
let internalBag = DisposeBag()
override init() {
super.init()
dataSource.onNext(createDataSourceOf(size: 7))
self.handleState()
}
private func createDataSourceOf(size:Int) -> [CellViewModelProtocol] {
var arr:[CellViewModelProtocol] = []
for _ in 0..<size {
let cell = CellVM()
arr.append(cell)
}
return arr
}
private func handleState() {
}
}
Maybe this code will help you:
extension TableViewController {
// called from viewDidLoad
func bind() {
let cells = (0..<7).map { _ in UUID() } // each cell needs an ID
let active = ReplaySubject<UUID>.create(bufferSize: 1) // tracks which is the currently active cell by ID
Observable.just(cells) // wrap the array in an Observable
.bind(to: tableView.rx.items(cellIdentifier: "Cell", cellType: Cell.self)) { _, element, cell in
// this subscription causes the inactive cells to turn off
active
.map { $0 == element }
.bind(to: cell.toggleSwitch.rx.isOn)
.disposed(by: cell.disposeBag)
// this subscription watches for when a cell is set to on.
cell.toggleSwitch.rx.isOn
.filter { $0 }
.map { _ in element }
.bind(to: active)
.disposed(by: cell.disposeBag)
}
.disposed(by: disposeBag)
}
}
Have a similar UI,so tested locally and it works.But not very neat code.
ProfileCellViewModel
struct ProfileCellViewModel {
// IMPORTANT!!!
var bibindRelay: BehaviorRelay<Bool>?
}
ProfileCell
final class ProfileCell: TableViewCell {
#IBOutlet weak var topLabel: Label!
#IBOutlet weak var centerLabel: Label!
#IBOutlet weak var bottomLabel: Label!
#IBOutlet weak var onSwitch: Switch!
public var vm: ProfileCellViewModel? {
didSet {
// IMPORTANT!!!
if let behaviorRelay = vm?.bibindRelay {
(onSwitch.rx.controlProperty(editingEvents: .valueChanged,
getter: { $0.isOn }) { $0.isOn = $1 } <-> behaviorRelay)
.disposed(by: self.rx.reuseBag)
}
}
}
}
ProfileViewModel
final class ProfileViewModel: ViewModel, ViewModelType {
struct Input {
let loadUserProfileStarted: BehaviorRelay<Void>
}
struct Output {
let userItems: BehaviorRelay<[ProfileCellViewModel]>
let chatRelay: BehaviorRelay<Bool>
let callRelay: BehaviorRelay<Bool>
}
let input = Input(loadUserProfileStarted: BehaviorRelay<Void>(value: ()))
let output = Output(userItems: BehaviorRelay<[ProfileCellViewModel]>(value: []),
chatRelay: BehaviorRelay<Bool>(value: false),
callRelay: BehaviorRelay<Bool>(value:false))
override init() {
super.init()
// IMPORTANT!!!
Observable.combineLatest(output.chatRelay,output.callRelay).pairwise().map { (arg0) -> Int in
let (pre, curr) = arg0
let preFlag = [pre.0,pre.1].filter { $0 == true }.count == 1
let currFlag = [curr.0,curr.1].filter { $0 == true }.count == 2
if preFlag && currFlag {
return [pre.0,pre.1].firstIndex(of: true) ?? 0
}
return -1
}.filter {$0 >= 0}.subscribe(onNext: { (value) in
[self.output.chatRelay,self.output.callRelay][value].accept(false)
}).disposed(by: disposeBag)
}
private func createProfileCellItems(user: User) -> [ProfileCellViewModel] {
// IMPORTANT!!!
let chatCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.chat(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.chatRelay)
// IMPORTANT!!!
let callCellViewModel = ProfileCellViewModel(topText: nil,
centerText: R.string.i18n.call(),
bottomText: nil,
switchStatus: true,
bibindRelay: output.callRelay)
return [roleCellViewModel,
teamCellViewModel,
statusCellViewModel,
sinceCellViewModel,
chatCellViewModel,
callCellViewModel]
}
}
I mark the codes you should pay attention to with // IMPORTANT!!!

Applying value (calculated with Firestore data) to label triggers "unwrapping an Optional value" crash

I have price list of ingredients, stored in Cloud Firestore. I want to calculate price for specific recepie, pass it to another VC and apply to label, so I wrote this code:
extension RecepiesViewController: UICollectionViewDelegate {
//Passing data to next VC
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ResultViewController
destinationVC.result = totalPrice
}
//Recepie cell pressed
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.item)!")
// Calculating price for recepie
func recepieCalculation(_ completion: #escaping (Double) ->()) {
for ingridients in cakeRecepies![indexPath.item].ingridientList {
db.collection("Okey")
.document(ingridients.name)
.getDocument() { (document, error) in
if let document = document, document.exists {
let data = document.data()
if let price = data!["price"] as? Double, let count = data!["count"] as? Int {
let priceForOne = price / Double(count)
self.totalPrice = self.totalPrice + priceForOne * Double(ingridients.count)
}
} else {
print("Document does not exist")
}
completion(self.totalPrice)
}
}
}
recepieCalculation {totalPrice in
self.performSegue(withIdentifier: "goToResult", sender: self)
}
}
}
Another VC code:
import Foundation
import UIKit
import RealmSwift
class ResultViewController: UIViewController {
#IBOutlet weak var resultLabel: UILabel!
var result: Double = 0.0 {
didSet {
print(result)
resultLabel.text = String(format: ".%f", result)
}
}
#IBOutlet weak var recepieImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
And at line resultLabel.text = String(format: ".%f", result) got error
Thread 1: Fatal error: Unexpectedly found nil while implicitly
unwrapping an Optional value
What could be wrong?
This is a common mistake.
At the moment the destination controller of the segue is going to be presented the outlets are not connected yet, so assigning the value to the label in the didSet observer crashes.
Solution:
Delete the observer
var result = 0.0
and assign the value in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
print(result)
resultLabel.text = String(format: ".%f", result)
}

Variable in renderer(_:didAdd:for:) not updating

I am working on a IPhone app that uses CoreML and ARKit simultaneously. The CoreML is supposed to recognize a number and the ARKit should detect a vertical plane (aka wall) and add some planes over that same wall with the content displayed on those planes depending on the recognised number.
So, the CoreML is working 100%. Everytime I "change" the number the topPrediction variable updates automatically ( so far so good ). The thing is that my variable in func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor does not update! What I mean is that the first number recognized by the CoreML is correctly sent to the renderer func and it works like a charm but if I turn the camera to another number it stills assumes that it's the first number! As you may see in the code, I even tried making a func getGabNum() -> Int and then calling it in the renderer func ( var num = getGabNum() ) but I continue getting the warning "Variable 'num' was never mutated; consider changing to 'let' constant" which means that something is not right. So guys, here is my code! Hope you can help me and thank you!
struct Room : Decodable {
let id : Int?
let num : Int?
//Adicionar Schedules
var horario = [Schedule]()
}
struct Schedule : Decodable {
let id : Int?
let hora_ini : Date?
let hora_fim : Date?
let descr : String?
private enum CodingKeys: String, CodingKey {
case id
case hora_ini
case hora_fim
case descr
}
}
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var debugLabel: UILabel!
#IBOutlet weak var debugTextView: UITextView!
#IBOutlet weak var sceneView: ARSCNView!
let dispatchQueueML = DispatchQueue(label: "com.hw.dispatchqueueml") // A Serial Queue
var visionRequests = [VNRequest]()
var room: Room?
var room_array: [[Int]] = [[17, 0], [43, 0], [120,0]]
var teste = 0
var num = -1
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
sceneView.scene = scene
configureLighting()
guard let selectedModel = try? VNCoreMLModel(for: SalasMLv6().model) else {
fatalError("Could not load model.")
}
let classificationRequest = VNCoreMLRequest(model: selectedModel, completionHandler: classificationCompleteHandler)
classificationRequest.imageCropAndScaleOption = VNImageCropAndScaleOption.centerCrop // Crop from centre of images and scale to appropriate size.
visionRequests = [classificationRequest]
loopCoreMLUpdate()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setUpSceneView()
}
func setUpSceneView()
{
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .vertical
sceneView.session.run(configuration)
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
func configureLighting()
{
sceneView.automaticallyUpdatesLighting = true
sceneView.autoenablesDefaultLighting = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
DispatchQueue.main.async {
// Do any desired updates to SceneKit here.
}
}
func loopCoreMLUpdate() {
dispatchQueueML.async {
self.updateCoreML()
self.loopCoreMLUpdate()
}
}
func updateCoreML() {
// Get Camera Image as RGB
let pixbuff : CVPixelBuffer? = (sceneView.session.currentFrame?.capturedImage)
if pixbuff == nil { return }
let ciImage = CIImage(cvPixelBuffer: pixbuff!)
// Prepare CoreML/Vision Request
let imageRequestHandler = VNImageRequestHandler(ciImage: ciImage, options: [:])
// Run Vision Image Request
do {
try imageRequestHandler.perform(self.visionRequests)
} catch {
print(error)
}
}
func classificationCompleteHandler(request: VNRequest, error: Error?) {
// Catch Errors
if error != nil {
print("Error: " + (error?.localizedDescription)!)
return
}
guard let observations = request.results else {
print("No results")
return
}
// Get Classifications
let classifications = observations[0...2] // top 3 results
.compactMap({ $0 as? VNClassificationObservation })
.map({ "\($0.identifier) \(String(format:" : %.2f", $0.confidence))" })
.joined(separator: "\n")
// Render Classifications
DispatchQueue.main.async {
// Display Debug Text on screen
self.debugTextView.text = "TOP 3 PROBABILITIES: \n" + classifications
// Display Top Symbol
var symbol = "❌"
var gabNum: Int?
let topPrediction = classifications.components(separatedBy: "\n")[0]
let topPredictionName = topPrediction.components(separatedBy: ":")[0].trimmingCharacters(in: .whitespaces)
// Only display a prediction if confidence is above 90%
let topPredictionScore:Float? = Float(topPrediction.components(separatedBy: ":")[1].trimmingCharacters(in: .whitespaces))
if (topPredictionScore != nil && topPredictionScore! > 0.05) {
if (topPredictionName == "120") {
symbol = "1️⃣2️⃣0️⃣"
gabNum = 120
self.teste = gabNum!
}
if (topPredictionName == "43") {
symbol = "4️⃣3️⃣"
gabNum = 43
self.teste = gabNum!
}
if (topPredictionName == "17") {
symbol = "1️⃣7️⃣"
gabNum = 17
self.teste = gabNum!
}
}
if let gn = gabNum {
// get room from REST
let jsonURL = "someURL\(gn)"
guard let url = URL(string: jsonURL) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil{
print("error)")
return
}
do {
self.room = try JSONDecoder().decode(Room.self, from: data!)
}catch{
print(“Decoder Error”)
}
}.resume()
}
self.debugLabel.text = symbol
}
}
// MARK: - HIDE STATUS BAR
override var prefersStatusBarHidden : Bool { return true }
func getGabNum() -> Int {
return self.teste
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
{
guard room != nil else {
print("room == nil")
return
}
guard let planeAnchor = anchor as? ARPlaneAnchor else {
return
}
num = getGabNum()
if( num == room_array[0][0] && room_array[0][1] == 1 ){
return
}else{
if( num == room_array[1][0] && room_array[1][1] == 1 ){
return
}else{
if( num == room_array[2][0] && room_array[2][1] == 1 ){
return
}else{
var i = 0
for horario in (self.room?.horario)!{
// Planes and Nodes Stuff Right Here
}
switch self.room?.num{
case 17: room_array[0][1] = 1
case 43: room_array[1][1] = 1
case 120:room_array[2][1] = 1
}
}
}
}
}
}

Fetching Tweets with Swift IOS

I'm practicing on a sample application that has a social feed page. I'm trying to display each tweet with the corresponding media. I was able to get the text and media but not as one tweet and the further I could get is displaying the media link. Any help on how to get the tweet with the media displayed would be appreciated. To make it clearer the user should be able to view the text and any picture/video from the application without the need to open any links.
import UIKit
class ViewController: UIViewController,
UITableViewDelegate,UITableViewDataSource {
//importing objects
#IBOutlet weak var mytextfield: UITextField!
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var myimageView: UIImageView!
#IBOutlet weak var myTableview: UITableView!
#IBOutlet weak var myScroll: UIScrollView!
var tweets:[String] = []
//Activity Indicator
var activityInd = UIActivityIndicatorView()
func startA()
{
UIApplication.shared.beginIgnoringInteractionEvents()
activityInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
activityInd.center = view.center
activityInd.startAnimating()
view.addSubview(activityInd)
}
//setting table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MyTableViewCell
cell.mytextview.text = tweets[indexPath.row]
return cell
}
#IBAction func mysearchbutton(_ sender: UIButton) {
if mytextfield.text != ""
{
startA()
let user = mytextfield.text?.replacingOccurrences(of: " ", with: "")
getStuff(user: user!)
}
}
//Create a function that gets all the stuff
func getStuff(user:String)
{
let url = URL(string: "https://twitter.com/" + user)
let task = URLSession.shared.dataTask(with: url!) { (data,response, error) in
if error != nil
{
DispatchQueue.main.async
{
if let errorMessage = error?.localizedDescription
{
self.myLabel.text = errorMessage
}else{
self.myLabel.text = "There has been an error try again"
}
}
}else{
let webContent:String = String(data: data!,encoding: String.Encoding.utf8)!
if webContent.contains("<title>") && webContent.contains("data-resolved-url-large=\"")
{
//get user name
var array:[String] = webContent.components(separatedBy: "<title>")
array = array[1].components(separatedBy: " |")
let name = array[0]
array.removeAll()
//getprofile pic
array = webContent.components(separatedBy: "data-resolved-url-large=\"")
array = array[1].components(separatedBy: "\"")
let profilePic = array[0]
print(profilePic)
//get tweets
array = webContent.components(separatedBy: "data-aria-label-part=\"0\">")
//get tweets media
// array = webContent.components(separatedBy: "data-pre-embedded=\"true\" dir=\"ltr\" >")
array.remove(at: 0)
for i in 0...array.count-1
{
let newTweet = array[i].components(separatedBy: "<")
array[i] = newTweet[0]
}
self.tweets = array
DispatchQueue.main.async {
self.myLabel.text = name
self.updateImage(url: profilePic)
self.myTableview.reloadData()
self.activityInd.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
}else{
DispatchQueue.main.async {
self.myLabel.text = "User not found"
self.activityInd.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
}
}
}
task.resume()
}
//Function that gets profile pic data
func updateImage(url:String)
{
let url = URL(string: url)
let task = URLSession.shared.dataTask(with: url!){ (data, response, error) in
DispatchQueue.main.async
{
self.myimageView.image = UIImage(data: data!)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myScroll.contentSize.height = 1000
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
#SYou can use TwitterKit SDK in iOS for your App. Twitter SDK is is fully capable to fulfil your needs. Whatever feed functionality you want you just need to configure it in twitter kit.
When showing Tweets you can implement these features for your feed :
The style (dark or light)
Colors (text, links, background)
Action Buttons
The delegate (TWTRTweetViewDelegate) to be notified of user interaction with the Tweet
To Show tweets you can do this :
For showing tweets you have two options :
You can load any public tweets (Attention : For Showing Public Tweets You need Public Tweet IDs)
Swift 4
For e.g
//
// PublicTweets.swift
// TwitterFeedDemo
//
// Created by User on 21/12/17.
// Copyright © 2017 Test Pvt. Ltd. All rights reserved.
//
import UIKit
import TwitterKit
class PublicTweets : UITableViewController {
// setup a 'container' for Tweets
var tweets: [TWTRTweet] = [] {
didSet {
tableView.reloadData()
}
}
var prototypeCell: TWTRTweetTableViewCell?
let tweetTableCellReuseIdentifier = "TweetCell"
var isLoadingTweets = false
override func viewDidLoad() {
super.viewDidLoad()
if let user = Twitter.sharedInstance().sessionStore.session()?.userID {
Twitter.sharedInstance().sessionStore.logOutUserID(user)
}
self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
// Create a single prototype cell for height calculations.
self.prototypeCell = TWTRTweetTableViewCell(style: .default, reuseIdentifier: tweetTableCellReuseIdentifier)
// Register the identifier for TWTRTweetTableViewCell.
self.tableView.register(TWTRTweetTableViewCell.self, forCellReuseIdentifier: tweetTableCellReuseIdentifier)
// Setup table data
loadTweets()
}
func loadTweets() {
// Do not trigger another request if one is already in progress.
if self.isLoadingTweets {
return
}
self.isLoadingTweets = true
// set tweetIds to find
let tweetIDs = ["944116014828138496","943585637881352192","943840936135741440"];
// Find the tweets with the tweetIDs
let client = TWTRAPIClient()
client.loadTweets(withIDs: tweetIDs) { (twttrs, error) -> Void in
// If there are tweets do something magical
if ((twttrs) != nil) {
// Loop through tweets and do something
for i in twttrs! {
// Append the Tweet to the Tweets to display in the table view.
self.tweets.append(i as TWTRTweet)
}
} else {
print(error as Any)
}
}
}
}
// MARK
// MARK: UITableViewDataSource UITableViewDelegate
extension PublicTweets {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of Tweets.
return tweets.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve the Tweet cell.
let cell = tableView.dequeueReusableCell(withIdentifier: tweetTableCellReuseIdentifier, for: indexPath) as! TWTRTweetTableViewCell
// Assign the delegate to control events on Tweets.
cell.tweetView.delegate = self
cell.tweetView.showActionButtons = true
// Retrieve the Tweet model from loaded Tweets.
let tweet = tweets[indexPath.row]
// Configure the cell with the Tweet.
cell.configure(with: tweet)
// Return the Tweet cell.
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let tweet = self.tweets[indexPath.row]
self.prototypeCell?.configure(with: tweet)
return TWTRTweetTableViewCell.height(for: tweet, style: TWTRTweetViewStyle.compact, width: self.view.bounds.width , showingActions:true)
}
}
extension PublicTweets : TWTRTweetViewDelegate {
//Handle Following Events As Per Your Needs
func tweetView(_ tweetView: TWTRTweetView, didTap url: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTapVideoWith videoURL: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTap image: UIImage, with imageURL: URL) {
}
func tweetView(_ tweetView: TWTRTweetView, didTap tweet: TWTRTweet) {
}
func tweetView(_ tweetView: TWTRTweetView, didTapProfileImageFor user: TWTRUser) {
}
func tweetView(_ tweetView: TWTRTweetView, didChange newState: TWTRVideoPlaybackState) {
}
}
You can also show other users tweets by just having their ScreenName or Twitter UserID
For e.g.
//
// SelfTweets.swift
// TwitterFeedDemo
//
// Created by User on 21/12/17.
// Copyright © 2017 Test Pvt. Ltd. All rights reserved.
//
import Foundation
import UIKit
import TwitterKit
class SelfTweets: TWTRTimelineViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let user = Twitter.sharedInstance().sessionStore.session()?.userID {
let client = TWTRAPIClient()
self.dataSource = TWTRUserTimelineDataSource.init(screenName:"li_ios", userID: user, apiClient: client, maxTweetsPerRequest: 10, includeReplies: true, includeRetweets: false)
}
}
}

Resources