I am trying trying to make a UISlider be enabled/disabled on button click, however Xcode is telling me that "UISlider does not have a member..."; basically, it doesn't work. When the button is clicked, it multiplies a variable by -1. If the variable is negative, then the UISlider will be enabled. If the variable is positive, then the UISlider will be disabled. The slider is called "userHue" and "opaque" is the label's name. The label is covering the slider. This is my code:
var noomber = 1
#IBAction func transparent(sender: UIButton)
{
noomber = -(noomber)
if(noomber<0)
{
opaque.backgroundColor = UIColor(hue: 0, saturation: 0, brightness: 0, alpha: 0)
userHue.userInteractionEnabled = true
}
else
{
opaque.backgroundColor = UIColor.whiteColor()
userHue.userInteractionEnabled = false
}
}
I have searched for at least an hour and I still cannot find
any answer to my question. Thanks in advance.
Solved my own problem: I only created an "action" for the slider, not an "outlet". Hence I couldn't access any of the slider's properties.
Related
On the following iOS screen the slider uses VoiceOver in a specific way
The 'SPEAKING RATE' UISlider reads information about the slider when clicked:
"Speaking rate 65% adjustable. Slide up or down to adjust the value"
I'd like my UISlider to read out similarly. Instead I get "Volume Control, 50%, Adjustable". What am I doing wrong? Here is my code:
import UIKit
class ViewController: UIViewController {
let slider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Set up the slider
slider.frame = CGRect(x: 50, y: 100, width: 200, height: 20)
slider.minimumValue = 0
slider.maximumValue = 100
slider.accessibilityIncrement()
slider.value = 50
// Set accessibility properties for the slider
slider.isAccessibilityElement = true
slider.accessibilityLabel = "Volume Control"
slider.accessibilityValue = "\(Int(slider.value))%"
slider.accessibilityTraits = .adjustable
// Add the slider to the view
view.addSubview(slider)
}
}
Don't worry, you're doing nothing wrong in your code. đ
Take a look in the user settings âš Accessibility - VoiceOver - Verbosity - Speak Hints...
... and you'll reach your goal to get VoiceOver for a UISlider to read like in the iOS Settings. đđ
Unfortunately, there's no notification to be aware of this value. đ
In iOS 14 I have configured a button to display a menu of questions. On its own the button is working perfectly. Hereâs how itâs configured:
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.showsMenuAsPrimaryAction = true
button.menu = self.menu
I have it set on a text fieldâs rightView. This is how it looks when the menu is displayed.
When a menu item is tapped, that question appears in a label just above the text field. It works great. ExceptâŚ.
I also want the menu to appear when the user taps in the text field. I donât want them to have to tap on the button specifically. In the old days of target-action this was easy: button.sendActions(for: .touchUpInside). I tried that and .menuActionTriggered and .primaryActionTriggered but none of those work. I think sendActions() is looking for the old selector based actions.
But thereâs a new sendActions(for: UIControl.Event) method. Hereâs what Iâm trying in textFieldShouldBeginEditing():
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
let tempAction = UIAction { action in
print("sent action")
}
menuButton.sendAction(tempAction)
print("textField tapped")
return false
}
Sure enough, âsent actionâ and âtextField tappedâ appear in the console when the text field is tapped. But I have no idea what UIAction I could send that means âdisplay your menuâ. I wish there was this method: send(_: UIControl.Event).
Any ideas on how to get the button to display its menu when the text field is tapped?
ps. yes, textFieldShouldBeginEditing will need to know if a question has been selected and in that case will need to allow editing. That part is easy.
From what I read, you cannot programmatically trigger UIContextMenuInteraction as interactions seem to be handled internally by itself unlike send(_: UIControl.Event)
I see this mentioned on this SO post here
Also in the docs it seems that we don't have access to interaction management Apple needs to decide 3D touch is available or default back to long tap
From the docs
A context menu interaction object tracks Force Touch gestures on devices that support 3D Touch, and long-press gestures on devices that don't support it.
Workaround
I can propose the following workaround for your use case. My example was created using frame instead of auto layout as faster and easier to demo the concept this way, however you will need to make adjustments using autolayout
1. Create the UI elements
// I assume you have a question label, answer text field and drop down button
// Set up should be adjusted in case of autolayout
let questionLabel = UILabel(frame: CGRect(x: 10, y: 100, width: 250, height: 20))
let answerTextField = UITextField(frame: CGRect(x: 10, y: 130, width: 250, height: 50))
let dropDownButton = UIButton()
2. Regular setup of the label and the text view first
// Just regular set up
private func configureQuestionLabel()
{
questionLabel.textColor = .white
view.addSubview(questionLabel)
}
// Just regular set up
private func configureAnswerTextField()
{
let placeholderAttributes = [NSAttributedString.Key.foregroundColor :
UIColor.lightGray]
answerTextField.backgroundColor = .white
answerTextField.attributedPlaceholder = NSAttributedString(string: "Tap to select a question",
attributes: placeholderAttributes)
answerTextField.textColor = .black
view.addSubview(answerTextField)
}
3. Add the button to the text view
// Here we have something interesting
private func configureDropDownButton()
{
// Regular set up
dropDownButton.setImage(UIImage(systemName: "chevron.down"), for: .normal)
dropDownButton.showsMenuAsPrimaryAction = true
// 1. Create the menu options
// 2. When an option is selected update the question label
// 3. When an option is selected, update the button frame
// Update button width function explained later
dropDownButton.menu = UIMenu(title: "Select a question", children: [
UIAction(title: "Favorite movie") { [weak self] action in
self?.questionLabel.text = action.title
self?.answerTextField.placeholder = "Add your answer"
self?.answerTextField.text = ""
self?.updateButtonWidthIfRequired()
},
UIAction(title: "Car make") { [weak self] action in
self?.questionLabel.text = action.title
self?.answerTextField.placeholder = "Add your answer"
self?.answerTextField.text = ""
self?.updateButtonWidthIfRequired()
},
])
// I right align the content and set some insets to get some padding from
// the right of the text field
dropDownButton.contentHorizontalAlignment = .right
dropDownButton.contentEdgeInsets = UIEdgeInsets(top: 0.0,
left: 0.0,
bottom: 0.0,
right: 20.0)
// The button initially will stretch across the whole text field hence we
// right aligned the content above
dropDownButton.frame = answerTextField.bounds
answerTextField.addSubview(dropDownButton)
}
// Update the button width if a menu option was selected or not
func updateButtonWidthIfRequired()
{
// Button takes the text field's width if the question isn't selected
if let questionText = questionLabel.text, questionText.isEmpty
{
dropDownButton.frame = answerTextField.bounds
return
}
// Reduce button width to right edge as a question was selected
dropDownButton.frame = CGRect(x: answerTextField.frame.width - 50.0,
y: answerTextField.bounds.origin.y,
width: 50,
height: answerTextField.frame.height)
}
4. End Result
Start with a similar view to yours
Then I tap in the middle of the text field
It displays the menu as intended
After selecting an option, the question shows in the label and the placeholder updates
Now I can start typing my answer using the text field and the button is only active on the right side since it was resized
And the button is still active
Final thoughts
Could be better to put this into a UIView / UITextField subclass
This was an example using frames with random values, adjustments need to made for autolayout
Edits from OP (too long for a comment):
I had tried setting contentEdgeInsets so the button was way over to the left but it covered up the placeholder text.
Your idea of simply adding the button as a subview of the text field was the key, but...
After selecting a question and resizing the button, if the text field was the first responder, tapping the button had no effect. The button was in the view hierarchy but the text field got the tap.
So, when a question is selected, I remove the button from its superview (the textfield) and add it to the textField's rightView. Then it would accept a tap even if the textField was the first responder.
And, as you suspected, I had to pin the button to the textField with constraints.
I have been looking around for a way to set the alignment of the segmented control titles to the left but I don't seem to be able to achieve what I want.
I have created this little function to change the frame of the subviews of the segment control.
It works at first.
func modifyFrameOfSegment() {
for segment in segmentedControl.subviews {
guard segment.subviews.isNotEmpty else { return }
segment.contentMode = .left
for label in segment.subviews where label is UILabel {
label.frame = CGRect(x: 0, y: label.frame.origin.y, width: label.frame.size.width, height: label.frame.size.height)
(label as! UILabel).textAlignment = .left
}
}
}
But everytime I select a new segment it resets the frames of all the subviews and center align all the titles again.
Is there a way to achieve a permanent left alignment for the segment titles in a segmented control?
Any tips or advice would be greatly appreciated.
Thank you for your time.
Let's use this method
self.segmentedControl.setContentPositionAdjustment(UIOffset(horizontal: -20, vertical: 0), forSegmentType: .left, barMetrics: .default)
And you can do what you want (Of course, you can change the horizontal & vertical value by your needs). Here is the result:
Update:
There's apparently no way to set the alignment of the items, but you can fake it by adjusting the position of each individual item using setContentOffset(_ offset: CGSize, forSegmentAt segment: Int). Here's a kludgy example:
class LeftSegmentedControl: UISegmentedControl {
var margin : CGFloat = 10
override func layoutSubviews() {
super.layoutSubviews()
leftJustifyItems()
}
func leftJustifyItems() {
let fontAttributes = titleTextAttributes(for: .normal)
let segments = numberOfSegments - 1
let controlWidth = frame.size.width
let segmentWidth = controlWidth / CGFloat(numberOfSegments)
for segment in 0...segments {
let title = titleForSegment(at: segment)
setWidth(segmentWidth, forSegmentAt: segment)
if let t = title {
let titleSize = t.size(withAttributes: fontAttributes)
let offset = (segmentWidth - titleSize.width) / 2 - margin
self.setContentOffset(CGSize(width: -offset, height: 0), forSegmentAt: segment)
}
}
}
}
Here's what it looks like:
There are a few caveats:
This version sets the segments to all have equal width, which might not be what you want.
I used a fixed left margin of 10px because it seems unlikely that you'd want to vary that, but you can obviously change it or make it a settable property.
Just because you can do this doesn't mean you should. Personally, I don't think it looks great, and it suffers in the usability department too. Users expect segmented control items to be centered, and left-justifying the items will make it harder for them to know where to tap to hit the segment. That seems particularly true for short items like the one labelled "3rd" in the example. It's not terrible, it just seems a little weird.
Original answer:
UIControl (of which UISegmentedControl is a subclass) has a contentHorizontalAlignment property that's supposed to tell the control to align its content a certain way, so the logical thing to do would be to set it like this:
let segmented = UISegmentedControl(items: ["Yes", "No", "Maybe"])
segmented.frame = CGRect(x:75, y:250, width:250, height:35)
segmented.contentHorizontalAlignment = .left
But that doesn't work â you still get the labels centered. If you've got a compelling use case for left-aligned segments, you should send the request to Apple.
One way you could work around this problem is to render your labels into images and then use the images as the segment labels instead of plain strings. Starting from the code in How to convert a UIView to an image, you could easily subclass UISegmentedControl to create images from the item strings.
I'm creating a basic sketching app using swift 1.2 and Xcode 6.4. The feature I'm working on is adding text to the image for future manipulation.
So far I have the text the user enters adding to the drawing via label, but it is being placed (I assume) behind the UIImage that is to be drawn upon. This is preventing the associated tap gesture from being recognised. The label is also obscured when drawn over.
How do I place the label on top of everything else to prevent it being drawn over? The end goal is to allow the user to move the label.
If I was using my usual web stack I'd set the z-index of the element to a high value via CSS.
Edit: Using view.bringSubviewToFront(label) prevents the text from being drawn over, but does not allow the tap gesture to be picked up.
Heres my code:
func printText(){
println(textString)
var label = UILabel(frame: CGRectMake(5, 100, 200, 50 ))
label.textAlignment = NSTextAlignment.Center
label.text = textString;
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap"))
label.addGestureRecognizer(tap)
self.view.addSubview(label);
}
func handleTap() {
print("tap working")
}
Any help much appreciated.
If you want z-index use:
label.layer.zPosition = 1;
Also you could play with bringToFront method on the parent view:
self.view.bringSubviewToFront(label);
You can check that function here :
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/
Also, you should add the following:
label.userInteractionEnabled = true
to enable touches.
Try using those in the parent view:
view.bringSubviewToFront(label)
view.sendSubviewToBack(imageView)
It all depends on what you can, or want to do.
You can call label to front by
self.view.bringSubviewToFront(label)
or
You can send image view to the back by
self.view.sendSubviewToBack(imageView)
I'm new to Objective C and swift (I guess we are all new to swift) but I am trying to make a UIButton appear and disappear in different locations on the screen in my app. This is what I've tried in one of my view controllers so far but it doesn't seem to work.
func addButton() {
var start: CFTimeInterval
var elapsedTime:CFTimeInterval
let Button = UIButton()
let picture = UIImage(named: "picture.png")
Button.setImage(picture, forState: UIControlState.Normal)
Button.frame = CGRectMake(0, 142, 106.6, 106.5)
self.view!.addSubview(Button)
while (elapsedTime < 1.0) {
elapsedTime = CACurrentMediaTime() - start
}
Button.removeFromSuperView()
}
You could use the convenient GCD API for the timing
dispatch_after(dispatch_time_t(1.0), dispatch_get_main_queue(), {
button.removeFromSuperView()
})
If it is always the same button, it would be better to create a variable or outlet and just recycle the button (you just let it appear and disappear by setting the alpha or the hidden property. If it is just supposed to be blinking, you could use a basic CAAnimations instead.
NB: Please get into the habit of using variable names that start with small letters, or you will end up mistaking them for class names.