If-statements with UIImageViews - swift

I have made an array of UIImagesViews and I have made a code to assign them random pictures. I would like to make an if-statement if more than or 5 pictures have the same picture shown.
var images: [UIImageView] = [Image1, Image2, Image3, Image4, Image5, Image6, Image7, Image8, Image9]
The different pictures are named card1, card2, card3
let firstRandomNumber = arc4random_uniform(3) + 1
let firstRandomString:String = String(format: "card%i", firstRandomNumber)
self.Image1.image = UIImage(named: firstRandomString)
let secondRandomNumber = arc4random_uniform(3) + 1
let secondRandomString:String = String(format: "card%i", secondRandomNumber)
self.Image2.image = UIImage(named: secondRandomString)
All images have their own specific code like the ones above. Now, as I said, I would like to make an if-statement or switch-case if five or more of the Images have the same picture shown.
Edit: Here is the updated code;
let imageNames:NSArray = [firstRandomString, secondRandomString, thirdRandomString, fourthRandomString, fifthRandomString, sixthRandomString, seventhRandomString, eighthRandomString, ninthRandomString]
let maxRepeating = imageNames
.map {img in imageNames.reduce(0) { $1 == img ? $0 + 1 : $0} }
.reduce(0){ $1 > $0 ? $1 : $0 }
The code only has one error at .map {img in imageNames.reduce(0) { $1 == img ? $0 + 1 : $0} } saying;
Binary operator '==' cannot be applied to two 'Element' (aka 'AnyObject') operands
Edit: Here is another code I have tried from Christopher Kevin;
var correct = 0
func checkImageDuplication() {
let imageArray : NSArray = [firstRandomString, secondRandomString, thirdRandomString, fourthRandomString, fifthRandomString, sixthRandomString, seventhRandomString, eighthRandomString, ninthRandomString]
let imageDataArray = imageArray.map { (image) -> NSData in
return UIImagePNGRepresentation(image as! UIImage)!
}
let countSet = NSCountedSet(array: imageDataArray)
for imageData in imageDataArray {
let count = countSet.countForObject(imageData)
if count > 5 {
correct = 5
}
}
}
#IBAction func PlayerPRESSED(sender: AnyObject) {
if correct == 5 {
view.backgroundColor = UIColor.greenColor()
}
}
I tried the code but it will not execute the action. (view.backgroundColor) Anyone have any Idea what is wrong with this code?

Your logic here needs to be separated from the views.
Create a model in the same class that holds an array of UIImage. Use this array to populate your cards, and to check whether more than 5 images are the same. Example untested and unoptimised code:
var images = [UIImage]()
private func populateImages() {
// Fill images array, and perhaps populate imageViews
}
private func checkImageDuplication() {
let imageDataArray = imageArray.map { (image) -> NSData in
return UIImagePNGRepresentation(image)!
}
let countSet = NSCountedSet(array: imageDataArray)
for imageData in imageDataArray {
let count = countSet.countForObject(imageData)
if count > 5 {
// Handle here
}
}
}

I don't think you can compare UIImage objects, as each call to UIImage(named:) will generate another UIImage instance, even if the name is the same. I'd recommend generating an array of strings, and testing that array for repetitions, with something like this:
let maxRepeating = imageNames
.map {img in imageNames.reduce(0) { $1 == img ? $0 + 1 : $0} }
.reduce(0){ $1 > $0 ? $1 : $0 }

Related

Is there a way of writing this in XCode without 2000 if else statements?

I'm writing a check in app for my gym in XCode. I asked this a year and a half ago for Android: Is there a way around doing 2000 else if statements in this?
But now, I am reaching 2000 members and need to add more. Unfortunately, and obviously, Xcode is crashing when I try to add more lines.
My code is this:
#IBAction func displayBarcode(_ sender: UIButton) {
if nameTextField.text=="100001" {
imageView.image = UIImage(named: ("All Barcodes/a0001.jpg"));
}
else if nameTextField.text=="100002" { imageView.image = UIImage(named: ("All Barcodes/a0002.jpg"));}
else if nameTextField.text=="100003" { imageView.image = UIImage(named: ("All Barcodes/a0003.jpg"));}
else if nameTextField.text=="100004" { imageView.image = UIImage(named: ("All Barcodes/a0004.jpg"));}
else if nameTextField.text=="100005" { imageView.image = UIImage(named: ("All Barcodes/a0005.jpg"));}
...all the way down to 2000. I know, absolutely crazy, but it has worked. Is there a way to combine these into one if/else statement into a range, similar to the solution someone gave me for Android?
Thank you sincerely!
Just get the last 4 digits, convert to integer, check if your desired range contains your value and check if they are all digits:
let string = "100002"
let suffix = string.suffix(4)
let range = 1...2000
if let int = Int(suffix),
suffix.count == 4,
suffix.allSatisfy(("0"..."9").contains),
range ~= int {
let imageName = "All Barcodes/a\(suffix).jpg"
print(imageName)
}
This will print
All Barcodes/a0002.jpg
Here's a basic solution to split the string and use part of it in the image name:
#IBAction func displayBarcode(_ sender: UIButton) {
guard let input = nameTextField.text else {
return
}
guard input.count == 6, input.prefix(2) == "10" else {
return
}
let imageName = "All Barcodes/a\(input.suffix(4)).jpg"
if let image = UIImage(named: imageName) {
imageView.image = image
}
}
This does some very trivial validation (eg count == 6 and it checks the prefix for 10), which you could make more stringent if you needed it, but this works for the basic task.

How to randomize cards data from model array without repetition

I am displaying cards data from my model array, how do i randomize card data everytime i start the app and should not be repetitive. I am using ZLSwipeable view library for creating cards fro swipe feature. this is the function in which i have write all my code for swipe cards, and in the code wordsdata is the model array that i have declaredlike this var wordsdata = ModelRsult.
CardView is the view i am displaying my cards into
screenshot of my card display screen
in the image it is shown banana as the first data, now when i run the app again it should show me another data first and so on
my source code:
func nextCardView() -> UIView?
{
let arrayData = wordsData
if (self.colorIndex >= arrayData.count)
{
return nil
}
let cardView = CardView(frame: swipeableView.bounds)
if loadCardsFromXib
{
let contentView = Bundle.main.loadNibNamed("CardContentView", owner: self, options: nil)?.first! as! CardView
contentView.superVC = self
contentView.translatesAutoresizingMaskIntoConstraints = false
contentView.backgroundColor = cardView.backgroundColor
cardView.addSubview(contentView)
let data = arrayData[(self.colorIndex)] as ModelResult
contentView.wordModel = data
let savedValue = UserDefaults.standard.bool(forKey: "isLangChanged")
tempLangChanged = savedValue
if tempLangChanged == false
{
if let spanishName = data.cardWordEnglish, spanishName.count != 0
{
contentView.wordSpanish.text = spanishName
print(spanishName)
}
}
else
{
if let dict = data.cardWordImage, dict.count != 0
{
let url = WS_wordIconUrl + dict
contentView.wordImage.kf.indicatorType = .activity
contentView.wordImage.kf.setImage(with: URL(string: url))
}
if let spanishName = data.cardWordSpanish, spanishName.count != 0
{
contentView.wordSpanish.text = spanishName
print(spanishName)
}
}
contentView.soundBtn.addTarget(self, action:#selector(self.soundBtn(sender:)), for: .touchUpInside)
contentView.hideCard.addTarget(self, action:#selector(self.hideCard(sender:)), for: .touchUpInside)
contentView.soundBtn.tag = self.colorIndex
contentView.hideCard.tag = self.colorIndex
cardView.tag = self.colorIndex
constrain(contentView, cardView) { view1, view2 in
view1.left == view2.left
view1.top == view2.top
view1.width == cardView.bounds.width
view1.height == cardView.bounds.height
}
}
colorIndex += 1
return cardView
}
The best way for this problem is to shuffle the array where your cards are and then use it. In your case, I would shuffle it at the app start.
array.shuffle()

Swift optional syntax

I wrote some code that works, but uses a few too many forced unwraps. So I fixed it but there has to a better way...
var currentTask: Tasks?
var photoArray: [Photo]?
#IBOutlet var photoButton: [TaskImageButton]!
photoArray = Array(currentTask?.photos) as? [Photo]
if photoArray!.count > 0 {
for i in (photoArray?.indices)! {
photoButton[i].setImage(UIImage(data: photoArray![i].imageData!), for: .normal)
}
}
My attempted solution:
if let photoArraySize = photoArray?.count {
if photoArraySize > 0 {
for i in (photoArray?.indices)! {
if let photoData = photoArray?[i].imageData {
photoButton[i].setImage(UIImage(data: photoData), for: .normal)
}
}
}
}
A better way is to declare the photos array as non-optional
var photoArray = [Photo]()
...
photoArray = (Array(currentTask?.photos) as? [Photo]) ?? []
...
for (index, photoData) in photoArray.enumerated() where photoData.imageData != nil {
photoButton[index].setImage(UIImage(data: photoData.imageData!), for: .normal)
}
Unwrap photoArray once and the rest of the code is much simpler:
if let photoArray = photoArray {
for i in photoArray.indices {
photoButton[i].setImage(UIImage(data: photoArray[i].imageData), for: .normal)
}
}
If imageData is optional, then you need:
if let photoArray = photoArray {
for i in photoArray.indices {
if let imageData = photoArray[i].imageData {
photoButton[i].setImage(UIImage(data: imageData), for: .normal)
}
}
}
And the line:
photoArray = Array(currentTask?.photos) as? [Photo]
can probably be written as:
photoArray = currentTask?.photos
But without more details about the types involved, it's hard to be sure.
This might be cleaner if you get the array out of optional first
Also you dont have to check if count is greater than 0 because if the array is empty (count = 0) then for loop wont even run.
if let array = photoArray {
for i in array.indices {
photoButton[i].setImage(UIImage(data: array[i].imageData), for: normal)
}
}
I dont know about Photo class but if imageData is also optional then:
if let array = photoArray {
for i in array.indices {
if let photoData = array[i].imageData {
photoButton[i].setImage(UIImage(data: photoData), for: .normal)
}
}
}

Convert HTML to NSAttributedString in Background?

I am working on an app that will retrieve posts from WordPress and allow the user to view each post individually, in detail. The WordPress API brings back the post content which is the HTML of the post. (Note: the img tags are referencing the WordPress URL of the uploaded image)
Originally, I was using a WebView & loading the retrieved content directly into it. This worked great; however, the images seemed to be loading on the main thread, as it caused lag & would sometimes freeze the UI until the image had completed downloading. I was suggested to check out the Aztec Editor library from WordPress; however, I could not understand how to use it (could not find much documentation).
My current route is parsing the HTML content and creating a list of dictionaries (keys of type [image or text] and content). Once it is parsed, I build out the post by dynamically adding Labels & Image views (which allows me to download images in background). While this does seem overly-complex & probably the wrong route, it is working well (would be open to any other solutions, though!) My only issue currently is the delay of converting an HTML string to NSAttributedText. Before adding the text content to the dictionary, I will convert it from a String to an NSAttributedString. I have noticed a few seconds delay & the CPU of the simulator getting up to 50-60% for a few seconds, then dropping. Is there anyway I could do this conversion on a background thread(s) and display a loading animation during this time?
Please let me know if you have any ideas or suggestions for a better solution. Thank you very much!
Code:
let postCache = NSCache<NSString, AnyObject>()
var yPos = CGFloat(20)
let screenWidth = UIScreen.main.bounds.width
...
func parsePost() -> [[String:Any]]? {
if let postFromCache = postCache.object(forKey: postToView.id as NSString) as? [[String:Any]] {
return postFromCache
} else {
var content = [[String:Any]]()
do {
let doc: Document = try SwiftSoup.parse(postToView.postContent)
if let elements = try doc.body()?.children() {
for elem in elements {
if(elem.hasText()) {
do {
let html = try elem.html()
if let validHtmlString = html.htmlToAttributedString {
content.append(["text" : validHtmlString])
}
}
} else {
let imageElements = try elem.getElementsByTag("img")
if(imageElements.size() > 0) {
for image in imageElements {
var imageDictionary = [String:Any]()
let width = (image.getAttributes()?.get(key: "width"))!
let height = (image.getAttributes()?.get(key: "height"))!
let ratio = CGFloat(Float(height)!/Float(width)!)
imageDictionary["ratio"] = ratio
imageDictionary["image"] = (image.getAttributes()?.get(key: "src"))!
content.append(imageDictionary)
}
}
}
}
}
} catch {
print("error")
}
if(content.count > 0) {
postCache.setObject(content as AnyObject, forKey: postToView.id as NSString)
}
return content
}
}
func buildUi(content: [[String:Any]]) {
for dict in content {
if let attributedText = dict["text"] as? NSAttributedString {
let labelToAdd = UILabel()
labelToAdd.attributedText = attributedText
labelToAdd.numberOfLines = 0
labelToAdd.frame = CGRect(x:0, y:yPos, width: 200, height: 0)
labelToAdd.sizeToFit()
yPos += labelToAdd.frame.height + 5
self.scrollView.addSubview(labelToAdd)
} else if let imageName = dict["image"] as? String {
let ratio = dict["ratio"] as! CGFloat
let imageToAdd = UIImageView()
let url = URL(string: imageName)
Nuke.loadImage(with: url!, into: imageToAdd)
imageToAdd.frame = CGRect(x:0, y:yPos, width: screenWidth, height: screenWidth*ratio)
yPos += imageToAdd.frame.height + 5
self.scrollView.addSubview(imageToAdd)
}
}
self.scrollView.contentSize = CGSize(width: self.scrollView.contentSize.width, height: yPos)
}
extension String {
var htmlToAttributedString: NSAttributedString? {
guard let data = data(using: .utf8) else { return NSAttributedString() }
do {
return try NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding:String.Encoding.utf8.rawValue], documentAttributes: nil)
} catch {
return NSAttributedString()
}
}
var htmlToString: String {
return htmlToAttributedString?.string ?? ""
}
}
( Forgive me for the not-so-clean code! I am just wanting to make sure I can achieve a desirable outcome before I start refactoring. Thanks again! )

How to reduce if-condition looping - Swift

I know it sounds crazy, but just curious how I can reduce the if loop iteration for following? I have tried using guard let but stucked at some place.
{
if arenaEventItems == nil || arenaEventItems.count <= 0 {
return
}
if (arenaEventItems.count > 0 && (self.arenaEvents?.monthsDictObjList.count)! > 0){
if (self.tableView != nil){
if let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath]{
if (self.tableView.indexPathsForVisibleRows!.count > 0){
let indexPath : IndexPath = self.tableView.indexPathsForVisibleRows!.first!
if let dict = self.arenaEvents?.monthsDictObjList[indexPath.row] {
if(self.arenaHeaderView != nil) && (dict.count) > 0 {
self.arenaHeaderView?.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in (self.arenaEvents?.uniqueMonthOnlyList)! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (self.arenaEvents?.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
}
}
}
}
}
}
You can reduce it like that, without any forced unwrapping or nesting:
guard let arenaEventItems = arenaEventItems,
!arenaEventItems.isEmpty,
let arenaEvents = self.arenaEvents,
!arenaEvents.monthsDictObjList.isEmpty,
let arenaHeaderView = self.arenaHeaderView,
let indexPath = self.tableView?.indexPathsForVisibleRows?.first,
let selectedMonthTitle = arenaEvents.monthsDictObjList[indexPath.row].keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
if let monthIndex = arenaEvents.uniqueMonthOnlyList.index(where: { selectedMonthTitle.contains($0) }) {
selectedMonthIndex = monthIndex
}
you replace if ... return with guard !... else return to avoid nesting
you replace .count > 0 with !...isEmpty as best practice
you replace multiple access to self.something? with if let something = self.something to avoid threading issues
you unloop for ... in ... { if (...) { ... } } to .index(where: ...)
You can combine all the conditions in "if" and get something like this:
if let eventItems = arenaEventItems,
eventItems.count > 0,
let events = self.arenaEvents,
!events.monthsDictObjList.isEmpty,
let tableView = self.tableView,
let arrVisibleRows = self.tableView.indexPathsForVisibleRows as? [IndexPath],
!arrVisibleRows.isEmpty,
let indexPath : IndexPath = arrVisibleRows.first,
let dict = events.monthsDictObjList[indexPath.row],
let headerView = self.arenaHeaderView,
!dict.isEmpty {
headerView.setMonthTitle(string: (dict.keys.first!))
let selectedMonthTitle = (dict.keys.first!)
for month in events.uniqueMonthOnlyList! {
if (selectedMonthTitle.contains(month)){
selectedMonthIndex = (events.uniqueMonthOnlyList.index(of: month)!)!
break
}
}
}
You should consider restructuring your code, your code is not readable and incomprehensible for anyone who look at it. Since, you are using Swift, it is really easy to write such code with guard ... else, if ... let
pattern.
Some improvements that you can do on class is have your view non nil ie make them implicitly unwrapped optional, since you will always be connecting them to storyboard.
#IBOutlet var tableView: UITableView!
#IBOutlet var arenaHeaderView: ArenaHeaderView!
Also, you have arrays which can go to nil, why do you want it to be nil. You could simply initialize an empty array and dictionaries. That way you can reduce some more comparison code like so,
arenaEventItems: [String: String] = [:]
With that changes and a bit of refactoring, you could possibly rewrite your code to something like this,
guard !arenaEventItems.isEmpty,
let arenaEvents = arenaEvents,
let indexPath = tableView.indexPathsForVisibleRows?.first,
let dict = arenaEvents.monthsDictObjList[indexPath.row],
let selectedMonthTitle = dict.keys.first
else {
return
}
arenaHeaderView.setMonthTitle(string: selectedMonthTitle)
for month in arenaEvents.uniqueMonthOnlyList where selectedMonthTitle.contains(month) {
if let selectedIndex = arenaEvents.uniqueMonthOnlyList.index(of: month) {
selectedMonthIndex = selectedIndex
break
}
}