Swift the figure of photo changes after retrieve from NSUserDefaults - ios

I want user to choose a photo from album, showing it on one imageView and store it in NSUserDefaults.
The below code means to retrieve this image from NSUserDefaults and show it in headSculptureImage
headSculptureImage.layer.cornerRadius = (headSculptureImage.frame.size.height)/2;
headSculptureImage.layer.masksToBounds = true
headSculptureImage.layer.borderWidth = 0;
This three is used to make this imageView to be a circle
if let userHeadSculpture = NSUserDefaults.standardUserDefaults().objectForKey("userHeadSculpture") {
let image = UIImage(data: userHeadSculpture as! NSData)
headSculptureImage.image = image
headSculptureImage.layer.cornerRadius = (headSculptureImage.frame.size.height)/2;
headSculptureImage.layer.masksToBounds = true
headSculptureImage.layer.borderWidth = 0;
}
The problem is when I choose a pic from album, it works like
what I expected when I reopen app is as same as the pic above, but it showed as
How to fix it?

It appears that you use constraints and use the NSUserDefaults code in viewDidLoad. If that is the case, since viewDidLoad is called before the constraints have loaded, your value of headSculptureImage.frame.size.height is a different one than what you would have with constraints, producing this sort of result.
In order to resolve that, try moving the code to viewDidLayoutSubviews, which is called after the constraints are all set up, to have the cornerRadius value exactly what you need.
Mind that viewDidLayoutSubviews is called quite frequently, so you might want to add some sort of a flag to make sure it is called only when you need it to.

Related

Detecting which ImageView has has been tapped

So i have 4 vStacks, each containing 9 ImageViews. Each ImageView represents one Card, alpha 0 is default. When a Card is Detected (with ARKit), my code sets the ImageView to alpha 1 so the user can see that the card has been scanned.
Now: I want to implement that when the user clicks on one of the ImageViews, an alert should pop up asking the user if he is sure he wants to delete the scanned card. My problem is, I have no idea what the best practice is to get the information that the card has been tapped and how to delete it without hardcoding.
In ViewDidLoad i set the images into the ImageVies like This:
//This repeats for all 36 ImageViews
imgView1.image = UIImage(named: "Eichel_6")
imgView2.image = UIImage(named: "Eichel_7")
/*When a image is detected with ARKit, this is what happens. Basically
*it pushes the corresponding reference name to an array called
* scannedCards, handles them, and removes them afterwards.
* spielPoints = gamepoints/points, spielmodus = gamemode
*/
func updateLabel() {
//print all cards in scanned cards
for card in scannedCards {
points += (DataController.shared.spielpoints[DataController.shared.spielmodus!]![card]! * DataController.shared.multCalculator[DataController.shared.spielmodus!]!)
}
scannedCards.removeAll()
}
I am a new to coding, I would be grateful if you correct me if my code snippets are bad, beside my question. Thank you in advance.
As has already been mentioned in comments, you should use a UICollectionView for this kind of work. #Fogmeister has promised to add an answer concerning that later, so I won't do that. But I can answer the actual question, even though it's not what you should do.
From your code I can see that you probably have outlets for all your imageViews (imgView1 ... imgView36) and set each image manually. To detect taps on any of these, you could do something like this:
func viewDidLoad(){
super.viewDidLoad()
let allImageViews = [imageView1, imageView2, .... imageView36]
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTapImageView(gesture:)))
allImageViews.forEach({$0.addGestureRecognizer(tapGestureRecognizer)})
}
#objc func didTapImageView(gesture:UITapGestureRecognizer){
guard let imageView = gesture.view as? UIImageView else { return }
//Here you can put code that will happen regardless of which imageView was tapped.
imageView.alpha = 0.0
//If you need to know exactly which imageView was tapped, you can just check
if imageView == self.imageView1{
//Do stuff only for imageView1
}else if imageView == self.imageView2{
//...
}//....
}
Again, this is not very good practice. If you go for UICollectionView instead, you don't have to have outlets for all your imageViews and you don't have to create a gestureRecognizer for handling events. But still, I hope this helped you understand general gestures better.

Swift 3 iOS 10 Reference to UIImageView to get Value for other function

I'm looking for a way to add codewise a way to reference to a UIImageView's value from another function.
I'm having a function which creates a new UIImageView for each instance in an array. Moreover, it also has an UITapGestureRecognizer added which calls a function when tapped. Now I want the function, which is called upon tap, to get the instance of that array which relates to the respective UIImageView.
I hope it's clear what I want to do. :)
Let's say you have an array of images:
let myImages = [UIImage(named: "image1"), UIImage(named: image2", UIImage(named: "image3"0]
And you have these images in three separate image views:
imageViewA.image = myImages[0]
imageViewB.image = myImages[1]
imageViewC.image = myImages[2]
Also, you've set up everything correctly that each image view calls the same function on a tap (we'll call it imageViewTapped()).
All you need to do is (1) properly set the tag property of each image view to point at the array index, (2) get the view that was tapped on, and (3) retrieve the image.
imageViewA.tag = 0
imageViewB.tag = 1
imageViewC.tag = 2
func imageViewTapped(_ recognizer:UITapGestureRecognizer) {
let tappedView = recognizer.view
let sourceImage = myImages[tappedView.tag]
}
I'd recommend setting everything up in a loop (tags, tap recognizer) and adding some type-casting in the function if needed. But I can't see your code to help you with that.

Swift Ios setting delegate without self as other view controller

I have seen everywhere that delegate variable is always set to self.
thirdClass.delegate = self
So i want something like
thirdViewController.delegate = firstViewControllerToDo
In my case,
let thirdClassVar = thirdClass()
thirdClassVar.namVar = "somnam"
thirdClassVar.delegate = firstViewControllerToDo
self.performSegueWithIdentifier("gtoFirstViewcontroller", sender: self)
When i click on button in second screen, it wil return back to first screen but in background I want to do some calculation in thirdClass and after completion of that, it shud display a message in first screen.
How can i achieve this or is there any other way of getting this done.
You want doing something but you do something with wrong way. I think you can do this process with UIButton and IBAction functions, change your algorith for this process.

Passing data between views via segmented bar

I know this is a frequently asked question throughout the forums and I hate to ask another seemingly simplistic question, but I can't manage to find a solution on data passing in regards to my specific circumstance.
Basically, I have a view controller embedded within a navigation controller; of which displays a segmented bar, acting as a 'profile selector'. After selection, I want a series of images on another view to be changed after different profiles are selected, but the data passing isn't seem to be working whatsoever. I'm unsure if delegate is required within my specific circumstance.
Essentially; I'd just love an example of how to pass a case value for a segmented bar so I would be able to perform a simple case statement like follows: (where the case values have been passed from the previous view controller)
#IBAction func chooseImage(sender: AnyObject) {
switch myPhotoSegment.selectedSegmentIndex {
//if first segment selected
case 0:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "1.jpg")
//if second segment selected
case 1:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "2.jpg")
//if third segment selected
case 2:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "3.jpg")
//if fourth segment selected
case 3:
//stop image animation if currently animating
newImageView.stopAnimating()
//update displayed image
newImageView.image = UIImage(named: "4.jpg")
//by default, no segment selected
default:
newImageView.image = nil
}
}
I know my problem is long and probably poorly explained, but any feedback would be greatly appreciated. I kind of struggle with the whole logic and understanding of passing data, so if you could break down the solution for me as simply as possible; that would be incredible.
To share data between those two classes they either need to know about each other or know about a shared object. For example, you could create a singleton data model that contains the properties you want to pass back and forth.
private let instance = MySingleton()
class MySingleton {
var somethingImInterestedIn: String?
var sharedInstance: MySingleton {
get {
return instance
}
}
}
Each class would get a reference to this by using:
MySingletion.sharedInstance
Once they have the reference to the singleton they can set or get any of the properties in that object. For your case you would want to store an enum instead of a String.

How do I set the accessibility label for a particular segment of a UISegmentedControl?

We use KIF for our functional testing, and it uses the accessibility label of elements to determine where to send events. I'm currently trying to test the behaviour of a UISegmentedControl, but in order to do so I need to set different accessibility labels for the different segments of the control. How do I set the accessibility label for a particular segment?
As Vertex said,
obj-c
[[[self.segmentOutlet subviews] objectAtIndex:3] setAccessibilityLabel:#"GENERAL_SEGMENT"];
swift
self.segmentOutlet.subviews[3].accessibilityLabel = "GENERAL_SEGMENT"
some advice so you don't go crazy like I did:
To scroll in accessibility mode swipe three fingers
The indexes of the segments are backwards than you would expect, i.e. the furthest segment to the right is the 0th index and the furthest to the left is the n'th index where n is the number of elements in the UISegmentControl
I'm just getting started with KIF myself, so I haven't tested this, but it may be worth a try. I'm sure I'll have the same issue soon, so I'd be interested to hear if it works.
First, UIAccessibility Protocol Reference has a note under accessibilityLabel that says:
"If you supply UIImage objects to display in a UISegmentedControl, you can set this property on each image to ensure that the segments are properly accessible."
So, I'm wondering if you could set the accessibilityLabel on each NSString object as well and be able to use that to access each segment with KIF. As a start, you could try creating a couple of strings, setting their accessibility labels, and using [[UISegmentedControl alloc] initWithItems:myStringArray]; to populate it.
Please update us on your progress. I'd like to hear how this goes
Each segment of UISegmentedControl is UISegment class instance which subclass from UIImageView. You can access those instances by subviews property of UISegmentedControl and try to add accessibility for them programmatically.
You can't rely on the index in the subviewsarray for the position. For customisation of the individual subviews I sort the subviews on their X Position before setting any propery.What would also be valid for accesibilityLbel.
let sortedViews = self.subviews.sorted( by: { $0.frame.origin.x < $1.frame.origin.x } )
sortedViews[0].accessibilityLabel = "segment_full"
sortedViews[1].accessibilityLabel = "segment_not_full"
This is an old question but just in case anyone else runs up against this I found that the segments automatically had an accessibility label specified as their text. So if two options were added of Option 1 and Option 2. A call to
[tester tapViewWithAccessibilityLabel:#"Option 2"];
successfully selected the segment.
The solutions with using an indexed subview is not working since you cannot rely on a correct order and it will be difficult to change the number of segments. And sorting by origin does not work, since the frame (at least for current versions) seems to be always at x: 0.
My solution:
(segmentedControl.accessibilityElement(at: 0) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 1"
(segmentedControl.accessibilityElement(at: 1) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 2"
(segmentedControl.accessibilityElement(at: 2) as? UIView)?.accessibilityLabel = "Custom VoiceOver Label 3"
Seems to work for me and has the correct order. You also do not rely on an image. Not that pretty either but maybe more reliable than other solutions.
This is an old question but just in case anyone else runs up against this I found that the segments automatically had an accessibility label specified as their text.
Further to Stuart's answer, I found it really useful when writing test cases to turn on 'Accessibility Inspector' on the Simulator (Settings -> General -> Accessibility -> Accessibility Inspector). You'd be surprised how many elements already have accessibility labels included, like in the standard iOS UI elements or even third party frameworks.
Note: Gestures will now be different - Tap to view accessibility information, double tap to select. Minimizing the Accessibility Inspector window (by tapping the X button) will return the gestures back to normal.
You guys want to see how Apple recommends it be done?
It's FUGLY.
This is from this example:
func configureCustomSegmentsSegmentedControl() {
let imageToAccessibilityLabelMappings = [
"checkmark_icon": NSLocalizedString("Done", comment: ""),
"search_icon": NSLocalizedString("Search", comment: ""),
"tools_icon": NSLocalizedString("Settings", comment: "")
]
// Guarantee that the segments show up in the same order.
var sortedSegmentImageNames = Array(imageToAccessibilityLabelMappings.keys)
sortedSegmentImageNames.sort { lhs, rhs in
return lhs.localizedStandardCompare(rhs) == ComparisonResult.orderedAscending
}
for (idx, segmentImageName) in sortedSegmentImageNames.enumerated() {
let image = UIImage(named: segmentImageName)!
image.accessibilityLabel = imageToAccessibilityLabelMappings[segmentImageName]
customSegmentsSegmentedControl.setImage(image, forSegmentAt: idx)
}
customSegmentsSegmentedControl.selectedSegmentIndex = 0
customSegmentsSegmentedControl.addTarget(self,
action: #selector(SegmentedControlViewController.selectedSegmentDidChange(_:)),
for: .valueChanged)
}
They apply the accessibility labels to images, and then attach the images. Not too different from the above answer.
another option if not willing to set accesibility label might be calculating the poistion of each segment part and use
[tester tapScreenAtPoint:segementPosition];
to trigger the actions
If you look at the segmented control thru the accessibility inspector, you find that the segments are UISegment objects. Moreover, they turn out to be direct subviews of the UISegmentedControl. That fact suggests the following insanely crazy but perfectly safe Swift 4 code to set the accessibility labels of the segments of a UISegmentedControl:
let seg = // the UISegmentedControl
if let segclass = NSClassFromString("UISegment") {
let segments = seg.subviews.filter {type(of:$0) == segclass}
.sorted {$0.frame.minX < $1.frame.minX}
let labels = ["Previous article", "Next article"] // or whatever
for pair in zip(segments,labels) {
pair.0.accessibilityLabel = pair.1
}
}
As mentioned in the accepted answer, adding accessibilityLabel to the text should do the trick:
let title0 = "Button1" as NSString
title0.accessibilityLabel = "MyButtonIdentifier1"
segmentedControl.setTitle("\(title0)", forSegmentAt: 0)
let title1 ="Button2" as NSString
title1.accessibilityLabel = "MyButtonIdentifier2"
segmentedControl.setTitle("\(title1)", forSegmentAt: 1)
XCode 12 / iOS 14.3 / Swift 5
This is an old post but I encountered the same problem trying to set accessibility hints for individual segments in a UISegmentedControl. I also had problems with some of the older solutions. The code that's currently working for my app borrows from replies such as those from matt and Ilker Baltaci and then mixes in my own hack using UIView.description.
First, some comments:
For my UISegmentedControl with 3 segments, the subview count is 3 in the viewDidLoad() and viewWillAppear() of the parent UIVIewController. But the subview count is 7 in viewDidAppear().
In viewDidLoad() or viewWillAppear() the subview frames aren't set, so ordering the subviews didn't work for me. Apparently Benjamin B encountered the same problem with frame origins.
In viewDidAppear(), the 7 subviews include 4 views of type UIImageView and 3 views of type UISegment.
UISegment is a private type. Working directly with the private API might flag your app for rejection. (see comment below)
type(of:) didn't yield anything useful for the UISegment subviews
(HACK!) UIView.description can be used to check the type without accessing the private API.
Setting accessibility hints based on X order tightly couples UI segment titles and hints to their current positions. If user testing suggests a change in segment order, then changes must be made both in the UI and in the code to set accessibility hints. It's easy to miss that.
Using an enum to set segment titles is an alternative to relying on X ordering set manually in the UI. If your enum inherits from String and adopts the protocols CaseIterable and RawRepresentable, then it's straightforward to create titles from the enum cases, and to determine the enum case from a segment title.
There's no guarantee the following will work in a future release of the framework, given the reliance on description.contains("UISegment") but it's working for me. Gotta move on.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// get only the UISegment items; ignore UIImageView
let segments = mySegmentedControl.subviews.compactMap(
{ $0.description.contains("UISegment") ? $0 : nil }
)
let sortedSegments = segments.sorted(
by: { $0.frame.origin.x < $1.frame.origin.x }
)
for i in 0 ..< sortedSegments.count {
let segment = sortedSegments[i]
// set .accessibilityHint or .accessibilityLabel by index
// or check for a segment title matching an enum case
// ...
}
}
On Private APIs and Rejection
I'm referring to the April 2016 comment from #dan in Test if object is an instance of class UISegment:
It's a private class. You can check it with [...
isKindOfClass:NSClassFromString(#"UISegment")] but that may get your
app rejected for using private api or stop working in the future if
apple changes the internal class name or structure.
Also:
What exactly is a Private API, and why will Apple reject an iOS App if one is used?
"App rejected due to non-public api's": https://discussions.apple.com/thread/3838251
As Vortex said, the array is right to left with [0] starting on the right. You can set every single accessibility option by accessing the subviews. Since the subviews are optional, it is good to pull out the subview first, and then assign the accessibility traits that you want. Swift 4 example for a simple two option segment control:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let rightSegment = segmentControl.subviews.first, let leftSegment = segmentControl.subviews.last else { return }
rightSegment.accessibilityLabel = "A label for the right segment."
rightSegment.accessibilityHint = "A hint for the right segment."
leftSegment.accessibilityLabel = "A label for the left segment."
leftSegment.accessibilityHint = "A hint for the left segment."
}

Resources