I wonder how you can add buttons (left and right) to change page controll views.
I'm working on this tutorial [1]: http://www.edumobile.org/iphone/iphone-programming-tutorials/pagecontrol-example-in-iphone/ . How can I add 2 simple buttons (left and right) to turn pages in addition to the swaping function in this example code?
I'm a programmer beginner so any kind of answer is highly appriciated! :)
thanks!
You can add two buttons to the view and when the button is clicked call a method to turn the page according to the button clicked.
UIButton *leftButton = [[UIButton alloc] init];
leftbutton.frame = leftButtonFrame;
[leftbutton setTitle:#"Left" forState:UIControlStateNormal];
[leftbutton addTarget:self action:#selector(leftbuttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[yourView addSubview:leftbutton];
UIButton *rightButton = [[UIButton alloc] init];
rightButton.frame = rightButtonFrame;
[rightButton setTitle:#"Right" forState:UIControlStateNormal];
[rightButton addTarget:self action:#selector(rightButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[yourView addSubview:rightButton];
- (void)leftButtonclicked:(id)sender
{
//Code to turn page left
}
- (void)rightButtonclicked:(id)sender
{
//Code to turn page right
}
I'm going to answer this in swift, but you should be able to translate.
The process is to find the PageControl view, add the two buttons and hide the buttons when appropriate (i.e. no previous button on first page).
I place the buttons on left and right side of PageControl. The default behavior is that touching there does a page back and page forward. So I set the buttons to enabled=false so that touches execute this default behavior.
First we need an enum to help locate buttons. Use values which won't be use elsewhere in view.
enum enumBtnTag: Int {
case tagPrev = 9991
case tagNext = 9992
}
var pageNbr = 0 //Needed to keep track of page being displayed
Now we will add our buttons in ViewDidLoad. First I locate the PageControl, then I look for buttons so as to not create twice. (ViewDidload could be called multiple times)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
for view in self.view.subviews {
if view is UIPageControl {
let curr:UIPageControl = view as! UIPageControl
curr.backgroundColor = UIColor.clear
curr.currentPageIndicatorTintColor = UIColor.red //Page Dot is red
curr.pageIndicatorTintColor = UIColor.black //Other dots are black
let pcSz = view.frame
let btnSz = CGSize(width: 35, height: 50) //Use your button size
if let _ = self.view.viewWithTag(enumBtnTag.tagNext.rawValue) as? UIButton {}
else { //Next Button not found
let Nbtn = UIButton(frame: CGRect(x: pcSz.width - btnSz.width, y: -15, width: btnSz.width, height: btnSz.height))
Nbtn.setTitle(">>", for: UIControlState.normal)
Nbtn.backgroundColor = UIColor.clear
Nbtn.setTitleColor(UIColor.brown, for: UIControlState.normal)
Nbtn.titleLabel?.font = UIFont(name: enumFontNames.MarkerFelt_Wide.rawValue, size: 60.0)
Nbtn.isEnabled = false //Allows touch to fall through to PageControl
Nbtn.tag = enumBtnTag.tagNext.rawValue
view.addSubview(Nbtn)
}
if let _ = self.view.viewWithTag(enumBtnTag.tagPrev.rawValue) as? UIButton {}
else { //Prev Button not found
let Pbtn = UIButton(frame: CGRect(x: 0, y: -15, width: btnSz.width, height: btnSz.height))
Pbtn.setTitle("<<", for: UIControlState.normal)
Pbtn.backgroundColor = UIColor.clear
Pbtn.setTitleColor(UIColor.brown, for: UIControlState.normal)
Pbtn.titleLabel?.font = UIFont(name: enumFontNames.MarkerFelt_Wide.rawValue, size: 60.0)
Pbtn.isEnabled = false
Pbtn.isHidden = true
Pbtn.tag = enumBtnTag.tagPrev.rawValue
view.addSubview(Pbtn)
}
}
}
}
Then I capture the page that is going to be displayed. The page might not be displayed (user didn't drag far enough), but that is handled later.
func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) {
if let identifier = pendingViewControllers[0].restorationIdentifier {
if let index = pages.index(of: identifier) {
pageNbr = index
}
}
}
Now we modify buttons in didFinishAnimating;
func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) {
if finished && completed {
if let button = self.view.viewWithTag(enumBtnTag.tagPrev.rawValue) as? UIButton {
if pageNbr > 0 {
button.isHidden = false
} else {
button.isHidden = true
}
}
if let button = self.view.viewWithTag(enumBtnTag.tagNext.rawValue) as? UIButton {
if pageNbr < pages.count - 1 {
button.isHidden = true
} else {
button.isHidden = false
}
}
}
}
Bonus code: I added a Save function at last page where the Next Button is. You need to set the button is enabled (so it registers the touch) and set a target (what ever function you want to execute); mine is "nextSegue".
and of course remove target when not on last page;
if pageNbr < pages.count - 1 {
//Not on last page. Use next button
button.setTitle(">>", for: UIControlState.normal)
button.removeTarget(self, action: #selector(nextSegue), for: UIControlEvents.touchUpInside)
button.isEnabled = false
} else {
//On last page. Use save button
button.setTitle("S", for: UIControlState.normal)
button.addTarget(self, action: #selector(nextSegue), for: UIControlEvents.touchUpInside)
button.isEnabled = true
}
Hope this helps someone.
Related
I have function which creates a drag line to connect 2 buttons to each other. This works fine but if some buttons overlap each other, it will select both if I drag over where they overlap. I only want to connect the top button.
I think the issue is with the sender.location selecting layers on top and below. Is there a way to tell the sender.location to only select the top view? Thanks for any input and direction
func addPanReconiser(view: UIView){
let pan = UIPanGestureRecognizer(target: self, action: #selector(DesignViewController.panGestureCalled(_:)))
view.addGestureRecognizer(pan)
}
#objc func panGestureCalled(_ sender: UIPanGestureRecognizer) {
let currentPanPoint = sender.location(in: self.view)
switch sender.state {
case .began:
panGestureStartPoint = currentPanPoint
self.view.layer.addSublayer(lineShape)
case .changed:
let linePath = UIBezierPath()
linePath.move(to: panGestureStartPoint)
linePath.addLine(to: currentPanPoint)
lineShape.path = linePath.cgPath
lineShape.path = CGPath.barbell(from: panGestureStartPoint, to: currentPanPoint, barThickness: 2.0, bellRadius: 6.0)
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point) {
button.layer.borderWidth = 4
button.layer.borderColor = UIColor.blue.cgColor
} else {
button.layer.borderWidth = 0
button.layer.borderColor = UIColor.clear.cgColor
}
}
case .ended:
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
//DO my Action here
lineShape.path = nil
lineShape.removeFromSuperlayer()
}
}
default: break
}
}
}
Note: some of the lines of codes are from custom extensions. I kept them in as they were self explanatory.
Thanks for the help
There is a way to walk around. It seems like you simply want your gesture end up at one button above all the others, thus by adding a var outside the loop and each time a button picked, comparing with the var of its level at z.
case .ended:
var pickedButton: UIButton?
for button in buttonArray {
let point = sender.location(in: button)
if button.layer.contains(point){
if pickedButton == nil {
pickedButton = button
} else {
if let parent = button.superView, parent.subviews.firstIndex(of: button) > parent.subviews.firstIndex(of: pickedButton!) {
pickedButton = button
}
}
}
}
//DO my Action with pickedButton here
lineShape.path = nil
lineShape.removeFromSuperlayer()
A UIView has a property called subViews where elements with higher indexes are in front of the ones with lower indexes. For instance, subView at index 1 is in front of subView with index 0.
That being said, to get the button that's on top, you should sort your buttonArray the same way subViews property of UIView is organized. Assuming that your buttons are all siblings of the same UIView (this might not be necessarily the case, but you can tweak them so you get them sorted correctly):
var buttonArray = view.subviews.compactMap { $0 as? UIButton }
Thus, keeping your buttonArray sorted that way, the button you want is the one that contains let point = sender.location(in: button) with higher index in the array.
open var buttonInit: ((_ index: Int) -> UIButton?)?
...
if let button: UIButton = self.buttonInit?(i) {
finButton = button
}else {
let button = UIButton(type: .custom)
button.setTitleColor(button.tintColor, for: [])
button.layer.borderColor = button.tintColor.cgColor
button.layer.borderWidth = 1
button.layer.cornerRadius = buttonHeight/2
finButton = button
}
I don't find any function description about buttonInit in AZDialogViewController. Does it mean button: UIButton = self.buttonInit?(i) will always be nil and finButton = button will not be executed?
The latter part of the code you quoted is in the setUpButton method:
fileprivate func setupButton(index i:Int) -> UIButton{
if buttonHeight == 0 {buttonHeight = CGFloat(Int(deviceHeight * 0.07))}
let finButton: UIButton
if let button: UIButton = self.buttonInit?(i) {
finButton = button
}else {
let button = UIButton(type: .custom)
button.setTitleColor(button.tintColor, for: [])
button.layer.borderColor = button.tintColor.cgColor
button.layer.borderWidth = 1
button.layer.cornerRadius = buttonHeight/2
finButton = button
}
This method is called here:
open func addAction(_ action: AZDialogAction){
actions.append(action)
if buttonsStackView != nil{
let button = setupButton(index: actions.count-1)
self.buttonsStackView.addArrangedSubview(button)
//button.frame = buttonsStackView.bounds
button.center = CGPoint(x: buttonsStackView.bounds.midX,y: buttonsStackView.bounds.maxY)
animateStackView()
}
}
From this we can see that buttonInit seems to be used to let the user of the library specify what kind of button they want as the action buttons. Another piece of evidence is that buttonInit is declared open, so it is likely that it is the client code who should set this, not the AZDialogViewController.
Plus, the README file showed this usage:
Use custom UIButton sub-class:
dialog.buttonInit = { index in
//set a custom button only for the first index
return index == 0 ? HighlightableButton() : nil
}
So to answer your question, the if branch will be executed if you set buttonInit.
#Huwell,
the documentation in the repository states to initialize the button in the following manner:
dialog.buttonInit = { index in
//set a custom button only for the first index
return index == 0 ? HighlightableButton() : nil
}
The button should be part of your DialogViewController.
apologies if this is a stupid question. I can't seem to get my slider to update its value as its being interacted with. (I'm going to point everyone to the very last method in this long code)
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = 7
return rect
}
}
class FactionButton: CustomSlider {
var factionSlider = CustomSlider(frame: CGRect(x: 15, y: 542, width: 386, height: 57))
func factionBalanceSlider(){
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = false
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)), for: .valueChanged)
}
func getSlider() -> CustomSlider {
return factionSlider
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = 10
return customBounds
}
#objc func recordFactionBalance(sender: CustomSlider){
//also calculates balance and adds it into the quiz data
print("hi")
print(sender.value) //It's this part that doesn't work
}
}
It's this bit nearest to the bottom that has the issue. (Everything else is fine) The action function doesn't seem to be triggered at all, even when I'm interacting with it. Neither print statements are being executed. Any ideas why?
Cheers
From the getSlider(), i can guess you are using this class as a utility to get the CustomSlider. So, i suspect you are adding the slider to the view as below,
let container = FactionButton()
container.factionBalanceSlider()
let slider = container.getSlider()
self.view.addSubview(slider)
If you will not add the container to the view which is set as the receiver for .valueChange event so it will not get any event. To receive events you also need to add the container in the view as below,
self.view.addSubview(container)
I created a UIBarButtonItem programmatically and the text is underlined. Is there a way to remove the underline?
let editButton = UIButton.init(type: .Custom)
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tabBarController?.title = "General Information"
editButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
editButton.addTarget(self, action: #selector(editButtonPressed(_:)), forControlEvents: .TouchUpInside)
editButton.frame.size = CGSize(width: 60, height: 30)
editButton.titleLabel?.adjustsFontSizeToFitWidth = true
let barButtonItem = UIBarButtonItem.init(customView: editButton)
self.tabBarController?.navigationItem.setRightBarButtonItem(barButtonItem, animated: true)
updateEditButtonTitle()
self.navigationController!.navigationItem.backBarButtonItem?.tintColor = UIColor.blackColor()
}
here is an image of the result I get, with the underline.
here is the function where I set the button's text. when it is pressed, it becomes a save button.
func updateEditButtonTitle() {
if let button = self.tabBarController?.navigationItem.rightBarButtonItem?.customView as? UIButton {
var title = ""
editButton.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.55)
editButton.layer.cornerRadius = 7.0
if isInEditMode {
title = "Save"
editButton.setTitleColor(UIColor.redColor(), forState: .Normal)
editButton.backgroundColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.5)
editButton.layer.cornerRadius = 7.0
editButton.frame.size = CGSize(width: 60, height: 30)
} else {
editButton.setTitleColor(UIColor.blueColor(), forState: .Normal)
title = "Edit"
}
button.setTitle(title, forState: .Normal)
}
}
Try this code ..
var attrStr: NSMutableAttributedString = yourBtnHere.attributedTitleForState(.Normal).mutableCopy()
//or whatever the state you want
attrStr.enumerateAttributesInRange(NSMakeRange(0, attrStr.characters.count), options: .LongestEffectiveRangeNotRequired, usingBlock: {(attributes: [NSObject : AnyObject], range: NSRange, stop: Bool) -> Void in
var mutableAttributes: [NSObject : AnyObject] = [NSObject : AnyObject](dictionary: attributes)
mutableAttributes.removeObjectForKey(.AttributeName)
attrStr.setAttributes(mutableAttributes, range: range)
})
With the inspector/IB: Select your UIButton.
Show the Attributes Inspector.
The Text settings should be in Attributed. Select the text, click on the fond item remove the Underlining setting it at none.
enter image description here
But..
Let me get this straight. Apple added an accessibility feature that lets users mark buttons with underlines if they want to.
You want a way to defeat this feature, specifically designed to help people with handicaps use their devices, when the feature is something that the user has to ask for.
Why?
It is very likely not possible using standard buttons. If you did figure out a way to do it, Apple would likely reject your app because it defeats a system function meant to help the disabled.
So the answer is: Don't do that.
I'm trying to keep a button selected while its audio is being played. the problem is the button won't change unless I change it in a loop (while(audioPlayer.playing){button.selcted=true}). In this case I can't use the app until audio has finished(for obvious reasons)
hopefully someone can help me
func addButton(number: String, x:CGFloat, y:CGFloat){
let button = UIButton(type: UIButtonType.Custom) as UIButton
button.setTitle(number, forState: .Normal)
button.frame = CGRectMake(x, y, 68, 212)
if let image = UIImage(named: number) {
button.setImage(image, forState: .Normal)
}
button.addTarget(self, action: "play:", forControlEvents:.TouchUpInside)
self.view.addSubview(button)
}
#IBAction func play(sender: UIButton) {
button=sender
button.selected = true
let audioFilePath = NSBundle.mainBundle().pathForResource(sender.currentTitle, ofType: "WAV")
if audioFilePath != nil {
let audioFileUrl = NSURL.fileURLWithPath(audioFilePath!)
do { audioPlayer = try AVAudioPlayer(contentsOfURL: audioFileUrl, fileTypeHint: nil)} catch _ { return }
audioPlayer?.delegate = self
audioPlayer.play()
} else {
print("audio file is not found")
}
}
I just wrote up a test app to see what you are actually trying to do here, set sender.enabled = false is what you are looking for. (The button as an animation that goes to the faded out gray that is set by not enabled, then goes back to the original color of enabled, setting it to false prevents the second part from happening)
Edit: In the end, the results that were desired was an accessible button with the gray text, so we changed the text highlight state to a gray using sender.setTitleColor(UIColor.grayColor(),forState:.Highlighted) and the user will set the highlighted variable to true in the Play function.