Looking to create a floating menu in Swift for an iOS application I am developing. Something along the lines of the little red circle menu as shown in the following image.
My initial thoughts were to extend the UIViewController class and add the respective drawing/logic there, however, the application is comprised of a few other controllers, more specifically the UITableViewController which in itself extends UIViewController. Is there perhaps a good place for an extension perhaps? Or is there a more eloquent way of drawing the menu on specific views without the mass duplication of menu related code?
The menu itself will be shown on most screens, so I need to selectively enable it. It'll also be somewhat contextual based on the view/screen the user is currently on.
Any awesome ideas?
You can create your own with the animations and all the things, or you can check this library
https://github.com/lourenco-marinho/ActionButton
var actionButton: ActionButton!
override func viewDidLoad() {
super.viewDidLoad()
let twitterImage = UIImage(named: "twitter_icon.png")!
let plusImage = UIImage(named: "googleplus_icon.png")!
let twitter = ActionButtonItem(title: "Twitter", image: twitterImage)
twitter.action = { item in println("Twitter...") }
let google = ActionButtonItem(title: "Google Plus", image: plusImage)
google.action = { item in println("Google Plus...") }
actionButton = ActionButton(attachedToView: self.view, items: [twitter, google])
actionButton.action = { button in button.toggleMenu() }
}
There is another alternative with this great library :
https://github.com/yoavlt/LiquidFloatingActionButton
You just have to implement the delegate and the dataSource in your ViewController:
let floatingActionButton = LiquidFloatingActionButton(frame: floatingFrame)
floatingActionButton.dataSource = self
floatingActionButton.delegate = self
You could use view controller containment. The menu can be its own view controller with its view laid transparently over top the content view controller.
For example this can be set up in the storyboard by dragging out two container views into a vanilla view controller.
Related
I've created a ViewController containing a user button, which is going to be present in several View Controllers in my application.
I'm adding this ViewController dynamically to the needed ViewControllers. The user button is shown, but it's not clickable. What am I doing wrong?
I've tried setting constraints to the view containing the button, setting the container view's frame, disabling user interaction in the container view (not in the button) and nothing seems to work
import UIKit
class ModulePageViewController: UIPageViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.addSharedButtonsSubView()
}
func addSharedButtonsSubView() {
let sharedButtons = storyboard?.instantiateViewController(withIdentifier: sharedButtonsViewControllerName)
view.addSubview((sharedButtons?.view)!)
sharedButtons?.view.frame = CGRect(x: view.frame.minX, y: view.frame.minY, width: view.frame.width, height: view.frame.height)
addChild(sharedButtons!)
sharedButtons?.didMove(toParent: self)
}
}
You can create a custom view (not ViewController) containing the button and just use it where you need in you app.
#LeCalore ...
I would recommend if you want to use a button or any more stuff on multiple View Controllers then you should just make a new ViewController with that button and whatever else you want on it then use it where ever you want.
ViewController -> Present As Pop Over (Presentation : Over Current Context)
I think that's a better approach atleast for starters.
Else, as user said ... you can make a custom view programatically and call it wherever you need that's another approach but it might give you a bit of trouble.
Open to others view if there's one better.
Gluck
So I have a custom view controller that displays a dialog with a couple of buttons.
When the view appears I want voiceover to read out some basic information describing the dialog.
To achieve this I made the parent view to be an accessible element and the subviews which are two buttons are also accessible elements.
My problems now is that the buttons are not clickable directly.
They must be reached only by swiping right on the screen.
class MyViewController: UIViewController {
let parent = UIView()
let button1 = UIButton()
let button2 = UIButton()
init() {
parent.addSubview(button1)
parent.addSubview(button2)
parent.isAccessibilityElement = true
button1.isAccessibilityElement = true
button2.isAccessibilityElement = true
parent.accessibilityLabel = "Message"
self.view.addSubview(parent)
self.view.accessibilityElements = [parent, button1, button2]
}
override func viewDidAppear(_ animated: Bool) {
}
}
If there is a better way to get voiceover to give description of the view when the opens, I am open to that too.
Also, the view needs to be a modal so that focus is trapped on the view.
To achieve this I made the parent view to be an accessible element and the subviews which are two buttons are also accessible elements.
That's definitely the problem: you can't have the parent view and its children accessible all together ⟹ see the example sheet of this explanation.
If a parent view is accessible, its children won't be seen by VoiceOver and conversely.
If there is a better way to get voiceover to give description of the view when the opens, I am open to that too.
Using VoiceOver, you must be as accurate and brief as possible.
The description of a view is provided by its elements when you explore the screen or by its title itself : in my view, you shouldn't read out a description that a perfect title should provide in addition to the correct implementation of the different components of your page.
There's a great presentation made by a blind person who explains how to write labels inside an app to be well understood.
Also, the view needs to be a modal so that focus is trapped on the view.
The best way to reach this purpose is to use the accessibilityViewIsModal property of your view ⟹ take a look at this example introduced during a WWDC session if need be.
You can post a notification with a message as parameter, so you would not need to set the parent view as an accessibility element. This would solve both of your problems.
Example code:
let parentVc = UIView()
let button1 = UIButton()
let button2 = UIButton()
init() {
parentVc.addSubview(button1)
parentVc.addSubview(button2)
button1.setTitle("btn1", for: .normal)
button2.setTitle("btn2", for: .normal)
button1.isAccessibilityElement = true
button2.isAccessibilityElement = true
self.view.addSubview(parentVc)
self.view.accessibilityElements = [button1, button2]
UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: "Message here");
}
I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)
Simple problem. I got button which perform segue to next view controller.
I want to write UI XCTest to tell me did it open view controller i wanted.
The UI Testing framework doesn't have access to your applications code which makes class assertions on instances impossible. You are not able to directly tell the class of the controller which is on screen.
However, if you think about your test a little differently you can make a very similar assertion. Write your tests as if you are the user. Your user doesn't care if he/she is looking at a ItemDetailViewController or a ItemListTableViewController so neither should your tests.
The user cares what's on the screen. What's the title? Or, what are the names of these buttons? Following that logic you are rewrite your test to assert based on those items, not the name of the coded class.
For example, if you are presenting your controller in a navigation stack you can assert the title.
let app = XCUIApplication()
app.buttons["View Item"].tap()
XCTAssert(app.navigationBars["Some Item"].exists)
Or, if the screen is presented modally but you know some static text or buttons, use those.
let app = XCUIApplication()
app.buttons["View Item"].tap()
XCTAssert(app.staticTexts["Item Detail"].exists)
XCTAssert(app.buttons["Remove Item"].exists)
Comment of Matt Green gave me a good idea. We can define an unused label/button, ideally inside a base view controller and assign it an accessibility label to perform a query to find out which view controller is presented.
public class BaseViewController: UIViewController {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
public override func viewDidLoad() {
super.viewDidLoad()
if let identifier = self.theClassName.split(separator: ".").last {
button.accessibilityIdentifier = String(identifier)
view.addSubview(button)
}
}
}
public class DatePickerViewController: BaseViewController {
...
}
func testExample() {
let app = XCUIApplication()
app.launch()
app.navigationBars.buttons["DateSelector"].tap()
XCTAssertTrue(app.buttons["DatePickerViewController"].exists)
}
Note that inorder to make this approach work you have to add the view you use to identify view controller, in this case a button, should be added as a sub view and has to have a non zero frame.
I am trying to display one view or another view inside the detail view of a master/detail based on a conditional.
These views will contain outlets and elements, so I would like to have view controllers for each that I can play with.
So I created a new UIViewController called AddPhotoViewController. This is how I add AddPhotoViewController.xib inside DetailViewController:
let photoVC = AddPhotoViewController(nibName: "AddPhotoViewController", bundle: nil)
let photoView = photoVC.view
photoVC.delegate = self
photoView.autoresizingMask = UIViewAutoresizing.FlexibleWidth
photoView.frame = area.bounds
area.addSubview(photoView)
The view loads properly in the detail view and looks like this:
AddPhotoViewController.xib's owner class has been set as well here:
When I tap the button, though the action is set properly in AddPhotoViewController to print a message, Xcode crashes.
Am I doing this correctly? Is there a more common practice for loading view X or view Y inside a view controller depending on user data?
Button action:
#IBAction func ButtonPressed(sender: AnyObject) {
println("worked!")
}
Button connection:
Console output:
I think you need to add the viewController:
addChildViewController(PhotoVC)
//and then
PhotoVC.didMoveToParentViewController(self)