I am in a real rough situation trying to squish this bug.
In a perfect world, I can press a "success" button, it plays a sound and vibrates, and changes the image in a UIImageView. This works perfectly until I press the home button and reopen the app. This glitch ALWAYS occurs.
Here is the relevant code:
#IBAction func Success(_ sender: Any) {
var boxNum = 1
generator.impactOccurred()
if "" == defaults.value(forKey: "frequency") as? String{
//One of 2 frequencies. It doesn't make a difference, however because the glitch always happens.
playVictorySound()
let formatter = DateFormatter()
for box in Boxes{
if box.image == UIImage(named:"blank.png"){
if "" == defaults.value(forKey: mode) as? String{
formatter.dateFormat = "dd.MM.yyyy"
let activeDate = Date()
defaults.set(Date(), forKey: "dateLast")
print(activeDate)
}
box.image = UIImage(named:"Arm.png")
defaults.set("Active", forKey: "boxState" + String(boxNum))
print("Active set for" + " boxState" + String(boxNum))
if "" == defaults.value(forKey: "mode") as? String{
successButton.isHidden = true
failButton.isHidden = true
}
break
}
else{
boxNum = boxNum + 1
print ("Added 1 to" + String(boxNum))
}
}
}
NOTE: This section has the user defaults that are set during the button press.
When the app is re-opened, the success button plays a sound (which is desired), and vibrates the phone (also desired), but the images do not update. In fact, the console prints a series of the else case.
Added 1 to38
Added 1 to39
Added 1 to40
Etc etc.
When any other modes or frequencies are set, the glitch also occurs. I am focused on fixing this case because I think I will be able to fix the others as well.
Is there some underlying reason causing this? I believe it may be because either something isn't being kept in memory, but I cannot figure out why. Thank you very much.
EDIT: Ok, I set the "blank" image to a selfie, and the app loads up "blank.png" for every square. There seems to be something wrong with another part of the code above.
I added print statements to help a bit and the if statement is the part that is failing, however, the ImageViews all have blank.png set as their image.
I fixed my issue using a workaround. I stopped checking the content of the UIImageView (Which for some reason didn't work), and instead used UserDefaults to see what the image should be.
I set a value in UserDefaults for all the possible images, and then check what the value of the default is.
Related
I work on an iOS app that displays images that often contain text, and I'm adding support for ImageAnalysisInteraction as described in this WWDC 2022 session. I have gotten as far as making the interaction show up and being able to select text and get the system selection menu, and even add my own action to the menu via the buildMenuWithBuilder API. But what I really want to do with my custom action is get the selected text and do a custom lookup-like thing to check the text against other content in my app.
So how do I get the selected text from an ImageAnalysisInteraction on a UIImageView? The docs show methods to check if there is selected text, but I want to know what the text is.
I was trying to solve the same problem. However, there doesn't currently seem to be any straightforward way to get selected text from ImageAnalysisInteraction. The closest thing seems to be the ImageAnalysis.transcript property, but it contains all the OCR text, not just what the user selected.
My solution was to capture the text whenever the user taps on the copy button on the selection menu. You can do this by observing clipboard changes, which allows you to copy the selected text from the clipboard whenever a change is detected.
See:
Get notified on clipboard change in swift
How to copy text to clipboard/pasteboard with Swift
Hope this help you
// Step -1
import Vision
// Step -2
// converting image into CGImage
guard let cgImage = imageWithText.image?.cgImage else {return}
// Step -3
// creating request with cgImage
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
// Step -4
let request = VNRecognizeTextRequest { request, error in
guard let observations = request.results as [VNRecognizedTextObservation],
error == nil else {return}
let text = observations.compactMap({
$0.topCandidates(1).first?.string
}).joined(separator: ", ")
print(text) // text we get from image
}
// step -5
request.recognitionLevel = VNRequestTextRecognitionLevel
try handler.perform([request])
For Reference and more details
I have some code I want to run after a particular date/time has passed. For example, if I want the code to run 7 days from now and the user opens the app at any time on day 7 or after the code will run but if they open the app before the beginning of day 7 nothing happens. Timers in the main runloop work but only if the app is still running in the background. I need a method that will work even if the user kills the app.
Your best option is to store it as local data Even though you only want the code to run once, the overhead is so low, the "check" will not impact the speed or feel of the application. Also this will allow you to run additional checks .. If someone deletes the app, for instance, and leaves the local storage behind. If they re-install you could theoretically "remember" that the application has been installed, and said code has already run (until the user clears application data)
Something like:
//Globally set key
struct defaultsKeys {
static let keyDate = "dateKey"
}
// Set the date in local storage
let defaults = UserDefaults.standard
defaults.set("Your Date String", forKey: defaultsKeys.dateKey)
// Get the date from local storage
let defaults = UserDefaults.standard
if let stringDate = defaults.string(forKey: defaultsKeys.dateKey) {
print(stringDate)
// Do your date comparison here
}
Very few lines of code, and even though the check happens every time the application starts .. The overhead is negligible.
You can either set the date you want your app to "remember" on your local storage or web service. Then, when the user opens your app, compare that date to current device time to determine if you should execute your code.
First, save the current time when you want. You can set the key name however you want.
UserDefaults.standard.setValue(Date(), forKey: "rememberTime")
And every time I open the app, You compare the current time with the saved time.
To do so, I created a function that compares time.
extension Date {
func timeAgoSince() -> Bool {
let calendar = Calendar.current
let unitFlags: NSCalendar.Unit = [.day]
let components = (calendar as NSCalendar).components(unitFlags, from: self, to: Date(), options: [])
if let day = components.day, day >= 7 {
// Returns true if more than 7 days have passed.
return true
}
return false
}
}
Recall the previously saved time and use the time comparison function.
let beforeTime: Date = (UserDefaults.standard.object(forKey: "rememberTime") as? Date)!
if beforeTime.timeAgoSince() {
// more than seven days later
...
} else {
...
}
If you have a problem, please leave a comment !
You can use the below sample code:
override func viewDidLoad() {
super.viewDidLoad()
let nextCodeRunDate = Date() + (7 * 24 * 60 * 60) // 7 Days
if let savedDate = UserDefaults.standard.value(forKey: "NEXT_DATE") as? Date {
if Date() > savedDate {
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}else {
// First time
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}
func runYourCode() {
// Your code
}
So I couldn't find a good answer that works for my problem. I am creating a SpriteKit game on iOS. Im just going to give you guys a quick overview of my setup and then go over the problem. I think that's the best way to explain this.
The Setup:
Basically in my didMove(to view:) method I call two helper functions: setupNodes() and setupLevel(). What these functions do is load the different scenes and SKSpriteNodes into memory and then add them to the scene. Here is the code I use to do this:
func setupNodes(){
doubleVent = (loadForegroundOverlayTemplate("DoubleVent").copy() as! SKSpriteNode)
}
func loadForegroundOverlayTemplate(_ fileName: String) -> SKSpriteNode{
let overlayScene = SKScene(fileNamed: fileName)!
let overlayTemplate = overlayScene.childNode(withName: "ForegroundOverlay")!.copy() as! SKSpriteNode
return overlayTemplate
}
This now stores the scene which is a child of an SKNode called "ForegroundOverlay". I then pass this to a function called "createForegroundOverlay()" like so:
let overlaySprite = doubleVent
createForegroundOverlay(doubleVent)
and then "createForegroundOverlay" adds the SKSpriteNode stored in doubleVent to the scene like so:
func createForegroundOverlay(_ overlayTemplate: SKSpriteNode) {
let foregroundOverlay = overlayTemplate.copy() as? SKSpriteNode
lastOverlayPosition.x = lastOverlayPosition.x + (lastOverlayWidth + ((foregroundOverlay?.size.width)!)/2)
lastOverlayWidth = (foregroundOverlay?.size.width)!/2.0
foregroundOverlay?.position = lastOverlayPosition
//TESTING PURPOSES
if TESTING_NODE_POS{
print("The last overlay position x:\(lastOverlayPosition.x), y:\(lastOverlayPosition.y)")
print("The last overlay width is \(lastOverlayWidth)")
print("Adding a foreground overlay \(count)")
count += 1
}
//TESTING PURPOSES
if TESTING_BOX_OUTLINE {
let foregroundPos = foregroundOverlay?.position
let boxLocation = foregroundOverlay?.childNode(withName: "Box1")?.position
if boxLocation != nil {
print("The location of the box \(String(describing: boxLocation))")
print("The foregroundLocation is \(String(describing: foregroundPos))")
print("last overlay position is \(lastOverlayPosition)")
}
}
//add the foreground overlay as a child of the foreground node
fgNode.addChild(foregroundOverlay!)
}
the variables for positioning - lastOverlayPosition, lastOverlayWidth etc.. - are just properties of my GameScene class used to know where and when to add the overlay that was passed.
The fgNode is a node that I stored from my GameScene.sks file like so:
let worldNode = self.childNode(withName: "World")!
bgNode = worldNode.childNode(withName: "Background")!
bgCityNode = worldNode.childNode(withName: "BackgroundCity")
cityBGOverlayTemplate = (bgCityNode.childNode(withName: "Overlay")!.copy() as! SKNode)
cityOverlayWidth = bgCityNode.calculateAccumulatedFrame().width
backgroundOverlayTemplate = (bgNode.childNode(withName: "Overlay")!.copy() as! SKNode)
backgroundOverlayWidth = backgroundOverlayTemplate.calculateAccumulatedFrame().width
fgNode = worldNode.childNode(withName: "Foreground")!
This was also done in my setupNodes() method.
The problem:
So maybe some of you guys have already seen the problem, but the problem is that when I launch my game it crashes and I get the message:
"Thread 1: EXC_BAD_ACCESS: (code = 1, address = 0x78)"
It is the exact same message every single time a crash occurs. I think I understand what the error is saying. Basically there is a pointer pointing to some location in memory (0x78) that has nothing there. So dereferencing that pointer obviously causes a fault. Here is where I get confused... This only happens about 50% of the time. when I run and build the project it builds successfully every time and then crashes 50% of the time with that error message. Secondly, this occurs at the very beginning. This is odd to me because how can some memory already be freed resulting in a bad pointer at the very beginning of the game. Also if the crash doesn't occur at launch then a crash never occurs except for when I reload the scene after the game over scene is displayed, which is basically the same as the game being relaunched.
With some time I narrowed the problem to one line of code:
fgNode.addChild(foregroundOverlay!)
if I comment this line out, no crash ever occurs (I tried building and running 50 times). The problem must be with the foregroundOverlay variable, which was setup using the code in the setup section of this discussion. So I have no idea how to fix this... Any Ideas??
P.S. it might also be worth noting that adding a child to the scene in this way only noticeably became a problem when I upgraded to xCode 10.0 and that I have considered using zombies, but I don't think that would help since the crash only happens at the launch of the game.
If I was unclear anywhere please let me know.
I'm working with the Photos framework, specifically I'd like to keep track of the current camera roll status, thus updating it every time assets are added, deleted or modified (mainly when a picture is edited by the user - e.g a filter is added, image is cropped).
My first implementation would look something like the following:
private var lastAssetFetchResult : PHFetchResult<PHAsset>?
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let fetchResult = lastAssetFetchResult,
let details = changeInstance.changeDetails(for: fetchResult) else {return}
let modified = details.changedObjects
let removed = details.removedObjects
let added = details.insertedObjects
// update fetch result
lastAssetFetchResult = details.fetchResultAfterChanges
// do stuff with modified, removed, added
}
However, I soon found out that details.changedObjects would not contain only the assets that have been modified by the user, so I moved to the following implementation:
let modified = modifiedAssets(changeInstance: changeInstance)
with:
func modifiedAssets(changeInstance: PHChange) -> [PHAsset] {
var modified : [PHAsset] = []
lastAssetFetchResult?.enumerateObjects({ (obj, _, _) in
if let detail = changeInstance.changeDetails(for: obj) {
if detail.assetContentChanged {
if let updatedObj = detail.objectAfterChanges {
modified.append(updatedObj)
}
}
}
})
return modified
}
So, relying on the PHObjectChangeDetails.assetContentChanged
property, which, as documentation states indicates whether the asset’s photo or video content has changed.
This brought the results closer to the ones I was expecting, but still, I'm not entirely understanding its behavior.
On some devices (e.g. iPad Mini 3) I get the expected result (assetContentChanged = true) in all the cases that I tested, whereas on others (e.g. iPhone 6s Plus, iPhone 7) it's hardly ever matching my expectation (assetContentChanged is false even for assets that I cropped or added filters to).
All the devices share the latest iOS 11.2 version.
Am I getting anything wrong?
Do you think I could achieve my goal some other way?
Thank you in advance.
I'm just starting developing apps with Swift 2.0, and as I'm working having problem with initiating a variable with the value of a text field in my app.
this is the function I'm having problems with. It's called when a button under the text field is pressed as submit.
#IBAction func checkName(sender: AnyObject) {
guard let name : String = nameField.text else { print("Name not valid.");return}
let checker = NameChecker()
let result = checker.nameChecker(name)
print(result)
}
this only thing this code returns on the XCode shell is "lldb". I also tried to use the debugger to figure out where I was messing up but unfortunately I found out that was harder than expected and I wasn't able to.
If you have any idea why my code is only returning "lldb" I would really appreciate is you could let me know since I've been experiencing this error quite often lately.