Dispatch for loading UIImages producing unexpected results - ios

I am building an app that has several images(Plans) that are loaded and setup on a scrollview with a page content. I am trying to load the first image on the main thread and then load the surrounding images from it's index on a background thread. All the UIImageViews are in an array (planImages) to reference when I need to set their images with the loaded data.
The problem is the images aren't always updating with the recently loaded images. This is my first time using Dispatch, so is there something I am doing wrong? Here is the section of code that is giving me the issue.
func setupPagesInScrollView(firstImage: Int)
{
let numberOfPages: CGFloat = CGFloat(selectedDetails!.numberOfPlans)
scrollView!.contentSize = CGSize(width: scrollView!.frame.width * numberOfPages, height: scrollView!.frame.height)
pageControl!.numberOfPages = selectedDetails!.numberOfPlans
if(planImages.count < selectedDetails!.numberOfPlans)
{
for index in planImages.count...selectedDetails!.numberOfPlans - 1
{
let iView = UIImageView()
iView.frame = CGRect(x: 0, y: 0, width: scrollView!.frame.width, height: scrollView!.frame.height)
planImages.append(iView)
scrollView!.addSubview(iView)
iView.translatesAutoresizingMaskIntoConstraints = false
iView.topAnchor.constraint(equalTo: scrollView!.topAnchor).isActive = true
iView.leftAnchor.constraint(equalTo: scrollView!.leftAnchor, constant: scrollView!.frame.width * CGFloat(index)).isActive = true
iView.heightAnchor.constraint(equalToConstant: scrollView!.contentSize.height).isActive = true
iView.widthAnchor.constraint(equalToConstant: scrollView!.frame.width).isActive = true
}
}
DispatchQueue.global(qos: .background).async
{
//Load first selected image
let path = self.selectedDetails!.dirPath + self.selectedDetails!.plansName
self.setupImageAtIndex(path: path, index: firstImage)
//Build Image data around first image on background threads
var mask = 1
let max = self.selectedDetails!.numberOfPlans
while firstImage + mask < max || firstImage - mask > 0
{
if(firstImage + mask < max)
{
self.setupImageAtIndex(path: path, index: firstImage + mask)
}
if(firstImage - mask >= 0)
{
self.setupImageAtIndex(path: path, index: firstImage - mask)
}
mask = mask + 1
}
//Remove extra images from memory if needed
if(self.planImages.count > self.selectedDetails!.numberOfPlans)
{
let dif = self.planImages.count - self.selectedDetails!.numberOfPlans
for _ in 1...dif
{
let index = self.planImages.count - 1
let plan = self.planImages[index]
self.planImages.remove(at: index)
plan.removeFromSuperview()
plan.image = nil
}
}
}
}
private func setupImageAtIndex(path: String, index: Int)
{
let imageName = index + 1 > 9 ? path + String(index + 1) : path + "0" + String(index + 1)
planImages[index].image = UIImage(contentsOfFile: imageName)
}

As per apple documentation You must update UI elements on the main queue.
Update your code like this
private func setupImageAtIndex(path: String, index: Int)
{
let imageName = index + 1 > 9 ? path + String(index + 1) : path + "0" + String(index + 1)
DispatchQueue.main.async {
planImages[index].image = UIImage(contentsOfFile: imageName)
}
}

Related

Print to printer via Bluetooth(non Airprint) in iOS?

I need to print an image from my iOS app to bluetooth thermal printer which is not AirPrint enabled. As we can not use UIKit print for non AirPrint printer, I chose to use 3rd party SDK.
Used this SDK and tried to print image where i can print small sized images, but when it comes to large one, it got crash on appending bytes saying index out of range, this is the function
`private func eachLinePixToCmd(src: [UInt8], nWidth: Int, nHeight: Int, nMode: Int) -> [UInt8] {
var data = [UInt8]
let p0 = [0, 0x80]
let p1 = [0, 0x40]
let p2 = [0, 0x20]
let p3 = [0, 0x10]
let p4 = [0, 0x08]
let p5 = [0, 0x04]
let p6 = [0, 0x02]
let nBytesPerLine: Int = (nWidth + 7) / 8
var k: Int = 0
for _ in 0..<nHeight {
data.append(ESC_POSCommand.beginPrintImage(xl: UInt8(nBytesPerLine % 0xff), xH: UInt8(nBytesPerLine / 0xff), yl: UInt8(1), yH: UInt8(0)).rawValue)
var bytes = [UInt8]()
for _ in 0..<nBytesPerLine {
bytes.append(UInt8(p0[Int(src[k])] + p1[Int(src[k + 1])] + p2[Int(src[k + 2])] + p3[Int(src[k + 3])] + p4[Int(src[k + 4])] + p5[Int(src[k + 5])] + p6[Int(src[k + 6])] + Int(src[k + 7])))
k = k + 8
}
data.append(bytes)
}
let rdata: [UInt8] = data.flatMap { $0 }
return rdata
}
}`
Please let me know if any other SDK available or where to make changes on appending bytes?
Hope for the reply!!
I faced the same issue with the print images, this is due to thermal printer limitations. Here I have compressed the image before sending it to print image.
func resizeWithWidth(width: CGFloat) - > UIImage ? {
let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width / size.width * size.height)))))
imageView.contentMode = .scaleAspectFit
imageView.image = self
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
guard
let context = UIGraphicsGetCurrentContext()
else {
return nil
}
imageView.layer.render( in: context)
guard
let result = UIGraphicsGetImageFromCurrentImageContext()
else {
return nil
}
UIGraphicsEndImageContext()
return result
}
//Set the width to 256 for example
let myImage = image.resizeWithWidth(width: 256) !
let compressData = myImage.jpegData(compressionQuality: 0.6)
//max value is 1.0 and minimum is 0.0
let compressedImage = UIImage(data: compressData!)
var ticImage = Ticket(.image(image, attributes: .alignment(.center)))
if bluetoothPrinterManager.canPrint {
bluetoothPrinterManager.print(ticImage)
}

Update image of SCNNode

if (wallFrame == nil) {
wallFrame = WallFrame()
wallFrame.name = "Image"
}
wallFrame.transform = SCNMatrix4((horizontalHit.anchor!.transform))
wallFrame.eulerAngles = SCNVector3Make(wallFrame.eulerAngles.x + (Float.pi / 2), wallFrame.eulerAngles.y, wallFrame.eulerAngles.z)
let positionz = SCNVector3Make(horizontalHit.worldTransform.columns.3.x, horizontalHit.worldTransform.columns.3.y, horizontalHit.worldTransform.columns.3.z)
wallFrame.position = positionz
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let image = UIImage(named: "deer.png")
elf.wallFrame.geometry?.firstMaterial?.diffuse.contents = image
}
arView.scene.rootNode.addChildNode(wallFrame)
I am creating a Custom SCNNode and trying to update the image content but it is not updating. How can I update SCNnode?

Crash specialized String.imageSize(),

I have a crash in this extension method of String:
func imageSize() -> CGSize {
// self = "https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2018-8-6/524x334-1_-E7VSb5T20mOouX.jpg"
var width = 0
var height = 0
let split0 = self.split(separator: "/")
if split0.count > 0 {
let split1 = split0.last?.split(separator: "-")
if (split1?.count)! > 0 {
let split2 = split1?.first?.decomposedStringWithCanonicalMapping.split(separator: "x")
width = (split2?.first?.decomposedStringWithCanonicalMapping.toInt())!
if (split2?.count)! > 1 {
// let split2 = split1![1].decomposedStringWithCanonicalMapping.split(separator: "-")
height = (split2?.last?.decomposedStringWithCanonicalMapping.toInt())!
}
}
}
return CGSize(width: width, height: height)
}
The crash is on line return CGSize(width: width, height: height)
I have created an NSString version like this to use the same above method:
#objc extension NSString {
func imageSize1() -> CGSize {
return (self as String).imageSize()
}
}
This is then called from obj-c code:
CGSize imageSize = [url imageSize1];
Examples of url are:
https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2019-02-07/675x900-1_-CdC62Y2hcV7208.jpg
https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2019-02-07/675x900-1_-697e3no8ec2E1I.jpg
https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2019-02-07/675x900-1_-8Af5D20wh9b62z.jpg
What this imageSize() method does is that it parses the image size from the url. The urls above contain the sizes 675x900 -> widthxheight.
In rare case we encounter a url where there is no information of the size and the url is not in the format above. So if the size is not found CGSize = (0 , 0) is returned.
I have tested this method on all expected scenarios.
But due to some reasons the method is causing crashes. May be I missed/messed something.
Here is the link to Crashlytics issue.
Any help would be appreciated.
Try don't use force unwrap !
let exampleString1 = "https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2018-8-6/524x334-1_-E7VSb5T20mOouX.jpg"
let exampleString2 = "https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2019-02-07/675x900-1_-697e3no8ec2E1I.jpg"
let exampleString3 = "https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2019-02-07/675x900-1_-CdC62Y2hcV7208.jpg"
extension String {
func imageSize() -> CGSize? {
// last url component
guard let imageName = self.split(separator: "/").last else { return nil }
guard let imageSizeString = imageName.split(separator: "-").first else { return nil }
let sizes = imageSizeString.split(separator: "x")
guard let first = sizes.first,
let last = sizes.last,
let wight = Int(String(first)),
let height = Int(String(last))
else { return nil }
return CGSize(width: wight, height: height)
}
}
exampleString1.imageSize() // Optional((524.0, 334.0))
exampleString2.imageSize() // Optional((675.0, 900.0))
exampleString3.imageSize() // Optional((675.0, 900.0))
Also try to use guard let and return nil if something wrong. For example Url schema can be changed
The crash is most likely due to force unwrapping optionals. There are several cases in your code where you're using it, which will lead to a runtime error if file name in your URL has a different format than you expect. Try
func imageSize() -> CGSize {
// self = "https://s3-eu-west-1.amazonaws.com/mimg.haraj.com.sa/userfiles30/2018-8-6/524x334-1_-E7VSb5T20mOouX.jpg"
var width = 0
var height = 0
let split0 = self.split(separator: "/")
if let split1 = split0.last?.split(separator: "-")
{
if let split2 = split1.first?.decomposedStringWithCanonicalMapping.split(separator: "x")
{
width = (split2.first?.decomposedStringWithCanonicalMapping.toInt()) ?? 0
if split2.count > 1 {
height = (split2.last?.decomposedStringWithCanonicalMapping.toInt()) ?? 0
}
}
}
return CGSize(width: width, height: height)
}

Swift 4 - How to update progressView in loop

I have created this progressView:
progress = UIProgressView(progressViewStyle: .default)
progress.center = view.center
progress.setProgress(0.5, animated: true)
view.addSubview(progress)
In my viewDidLoad method, I call getLandGradingImages and inside that I do a loop, inside that I call getLandGradingImage for each result in getLandGradingImages, what would be the best way to update the progressView I created during this entire process:
getLandGradingImages(jobNo: jobNo) { result in
//Define axis variables
var x = 25
var y = 80
//Define image counter variable
var counterImages = 0
var actualImageCounter = 0
//For each Lot Image
for item in result
{
self.getLandGradingImage(image: item["imageBytes"] as! String) { data in
//Create Image from Data
let image = UIImage(data: data)
//Add Image to Image View
let imageView = UIImageView(image: image!)
//Set Image View Frame
imageView.frame = CGRect(x: x, y: y, width: 100, height: 100)
//Define UITapGestureRecognizer
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.imageTapped(tapGestureRecognizer:)))
//Enable user interaction
imageView.isUserInteractionEnabled = true
//Assign tap gesture to image view
imageView.addGestureRecognizer(tapGestureRecognizer)
//Add Image to view
self.view.addSubview(imageView)
//Increase x axis
x = x + 115
//Increase image counter
counterImages = counterImages + 1
actualImageCounter = actualImageCounter + 1
//If image counter is equal to 3, create new line.
if(counterImages == 3)
{
//Increase y axis
y = y + 115
//Reset x axis
x = 25
//Reset image counter
counterImages = 0
}
if(actualImageCounter == result.count)
{
//self.stopIndicator()
}
}
}
}
Here are those two methods I am calling in this process:
func getLandGradingImages(jobNo:String, completionHandler:#escaping (_ result:Array<Dictionary<String, Any>>) -> Void) {
//Define array for returning data
var returnedResults = Array<Dictionary<String, Any>>()
//Call API
WebService().GetLandGradingImages(jobNo: jobNo)
{
(result: Array<Dictionary<String, Any>>) in
DispatchQueue.main.async {
//Return our results
returnedResults = result
completionHandler(returnedResults)
}
}
}
func getLandGradingImage(image:String, completionHandler:#escaping (_ result:Data) -> Void) {
//Define array for returning data
var returnedResults = Data()
//Call API
WebService().GetLandGradingImage(image: image)
{
(result: Data) in
DispatchQueue.main.async {
//Return our results
returnedResults = result
completionHandler(returnedResults)
}
}
}
For these calls, I am using Alamofire:
func GetLandGradingImage(image: String, completion: #escaping (_ result: Data) -> Void)
{
let imagePath : String = image
let url = URL(string: webserviceImages + imagePath)!
Alamofire.request(url).authenticate(user: self.appDelegate.username!, password: self.appDelegate.password!).responseData { response in
let noData = Data()
if(response.error == nil)
{
if let data = response.data {
completion(data)
}
}
else
{
completion(noData)
}
}
}
I have tried this:
var counter:Int = 0 {
didSet {
let fractionalProgress = Float(counter) / 100.0
let animated = counter != 0
progress.setProgress(fractionalProgress, animated: animated)
}
}
and inside the for loop:
DispatchQueue.global(qos: .background).async {
sleep(1)
DispatchQueue.main.async {
self.counter = self.counter + 1
}
}
Still nothing, my the progress view appears but does not get updated.

Why is my screen not rendering anything?

I have the following code. When I run this I am getting a blank screen. This block of code is inside sceneDidLoad() so they will get executed but not displaying anything. Am I missing something ?
let worldNode = SKNode()
let height = 100
let width = 100
let regions:[Float: String] = [-0.04: "sand", -0.08: "water", 0.9: "grass"]
let noiseSource = GKPerlinNoiseSource()
let noise: GKNoise = GKNoise(noiseSource: noiseSource)
let noiseMap: GKNoiseMap = GKNoiseMap(noise: noise)
let tileMapNode = SKTileMapNode()
tileMapNode.enableAutomapping = true
tileMapNode.numberOfRows = height
tileMapNode.numberOfColumns = width
worldNode.addChild(tileMapNode)
for y in 0 ... height {
for x in 0 ... width {
let currentHeight = noiseMap.value(atPosition: vector2(Int32(x), Int32(y)));
for (key, value) in regions {
if (currentHeight <= key) {
//colourMap [y * mapChunkSize + x] = regions[i];
let tileSize = CGSize(width: 32.0, height: 32.0)
let tileTexture = SKTexture(imageNamed: value)
let tileDef = SKTileDefinition(texture: tileTexture, size: tileSize)
let tileGroup = SKTileGroup(tileDefinition: tileDef)
tileMapNode.setTileGroup(tileGroup, forColumn: x, row: y)
print("Tiling: \(value)")
break;
}
}
}
}
self.addChild(worldNode)

Resources