I've customized a share button and put it in UINavigationbar to be a right bar button item, I want to click this button to present a UIActivityViewController, the code can be built successfully and run, but the share button cannot be clicked, nothing happens (even errors), just like the share button is disabled.
The demo picture:
Can anyone help me to solve it? Thank you in advance.
Code:
import UIKit
import Font_Awesome_Swift
class shareButton:UIButton{
var button:UIButton = UIButton()
init(button_frame: CGRect, connected: [UIButton]?){
super.init(frame:CGRect(x: 0, y: 0, width: 20, height: 20))
self.button.frame = button_frame
self.button.setFAIcon(icon: FAType.FAShareAlt, iconSize: 22, forState: .normal)
self.button.setFATitleColor(color: .darkGray)
self.button.addTarget(self, action:#selector(self.buttonPressed), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func construct() -> UIButton {
return self.button
}
#objc func buttonPressed() {
let url = NSURL.init(string: "http://www.probe-lab.com")
let items:[Any] = [url!]
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityViewController.excludedActivityTypes = [.print,
.assignToContact,.saveToCameraRoll,.addToReadingList,.openInIBooks]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController?.present(activityViewController, animated: true, completion: nil)
}
}
In viewcontroller I used the button like this:
override func viewDidLoad() {
super.viewDidLoad()
let btn = shareButton(button_frame: CGRect(x:0, y:0, width: 30, height:30), connected:nil ).construct()
let item = UIBarButtonItem(customView: btn)
self.navigationItem.rightBarButtonItem = item
}
First of all, please be sure to Capitalize class names. That is the convention in Swift. Now, moving on to the solution:
You don't need to subclass UIButton to do get your desired result.
Simply add the "buttonPressed" code as action for the barbuttonitem.
Trying to create a UIButton subclass that internally instantiates another UIButton and sends that button to the ViewController which then casts it into UIBarButtonItem is a very convoluted way of doing it.
You can download my solution from this repository:
https://github.com/asabzposh/StackOverFlow-48554026.git
If you desire to create a UIButton subclass, then do it this way:
import UIKit
class ShareButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
// Set title, background color, etc.
let imageView = UIImageView(frame: frame)
let image = UIImage(named: "p.jpeg")
imageView.image = image
imageView.contentMode = .scaleAspectFill
self.addSubview(imageView)
self.addTarget(self, action: #selector(internalButtonPressed), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
#objc func internalButtonPressed() {
let url = NSURL.init(string: "http://www.probe-lab.com")
let items:[Any] = [url!]
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityViewController.excludedActivityTypes = [.print, .assignToContact,.saveToCameraRoll,.addToReadingList,.openInIBooks]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController?.present(activityViewController, animated: true, completion: nil)
}
}
Then to use it in your ViewController:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// 1. Solution A
// let item = UIBarButtonItem(title: "Share", style: .plain, target: self, action: #selector(buttonPressed))
// 2. Solution B
let button = ShareButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
let item = UIBarButtonItem()
item.customView = button
// 3. Finally Add barbutton item
self.navigationItem.rightBarButtonItem = item
}
#objc func buttonPressed() {
let url = NSURL.init(string: "http://www.probe-lab.com")
let items:[Any] = [url!]
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityViewController.excludedActivityTypes = [.print, .assignToContact,.saveToCameraRoll,.addToReadingList,.openInIBooks]
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController?.present(activityViewController, animated: true, completion: nil)
}
}
Issue is with your custom class i just tested your custom class and run small test case on that, made some changes please have a look. no need to make var button:UIButton = UIButton() in share button , just override UIbutton method.I have changed the add target function as well.
class shareButton:UIButton{
override init(frame: CGRect) {
// set myValue before super.init is called
super.init(frame: frame)
//self.button.frame = frame
isUserInteractionEnabled = true
backgroundColor = UIColor.red
addTarget(self, action: #selector(buttonPressed1( sender:)), for: UIControlEvents.touchUpInside)
// set other operations after super.init, if required
backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func buttonPressed1(sender: UIButton) {
}
}
I got a solution and modified my code, add "var strongSelf:shareButton?" and "strongSelf = self", now it can run and present an activityViewController. I don't know if this is the best solution, but at least it works.
I found it works fine with iPhone devices and simulators but crashes with iPads, so add "activityViewController.popoverPresentationController?.sourceView = button" to solve it.
import UIKit
import Font_Awesome_Swift
class ShareButton:UIButton{
var button:UIButton = UIButton()
var strongSelf:ShareButton?
init(button_frame: CGRect, connected: [UIButton]?){
super.init(frame:CGRect(x: 0, y: 0, width: 20, height: 20))
self.button.frame = button_frame
self.button.setFAIcon(icon: FAType.FAShareAlt, iconSize: 22, forState: .normal)
self.button.setFATitleColor(color: .darkGray)
self.button.addTarget(self, action:#selector(self.buttonPressed), for: .touchUpInside)
strongSelf = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func construct() -> UIButton {
return self.button
}
#objc func buttonPressed() {
let url = NSURL.init(string: "http://www.probe-lab.com")
let items:[Any] = [url!]
let activityViewController = UIActivityViewController(activityItems: items, applicationActivities: nil)
activityViewController.excludedActivityTypes = [.print,
.assignToContact,.saveToCameraRoll,.addToReadingList,.openInIBooks]
activityViewController.popoverPresentationController?.sourceView = button
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.window?.rootViewController?.present(activityViewController, animated: true, completion: nil)
}
}
In any view I wanna use this share button just add the lines like this:
override func viewDidLoad() {
super.viewDidLoad()
let btn = ShareButton(button_frame: CGRect(x:0, y:0, width: 30, height:30), connected:nil ).construct()
let item = UIBarButtonItem(customView: btn)
self.navigationItem.rightBarButtonItem = item
}
Related
I need to add a UIView with size of tabbar but exactly above tabbar. This view allow user to come back to a started workout. My idea is holding data inside UIView and instantiate a View Controller with unfinished data when user clicks button. The problem is that when I want to instantiate VC my data in UIView become nil.
class BeforeRoutineClass {
// HERE I CREATE the UIView
func showWorkoutView(temporaryRoutine: Routine) {
guard let tabBar = navigationController.tabBarController else { return print("Tabbar is nil") }
let window = UIApplication.shared.keyWindow!
let height = tabBar.tabBar.frame.height
view2 = BackToWorkoutButton(frame: CGRect(x: 0, y: (tabBar.tabBar.frame.origin.y) - height, width: window.frame.width, height: height), routine: temporaryRoutine)
window.addSubview(view2!)
}
}
class BackToWorkoutButton: UIView {
var routine: Routine?
init(frame: CGRect, routine: Routine?, bobo: String?) {
self.routine = routine
super.init(frame: frame)
customInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private func customInit() {
let xibView = Bundle.main.loadNibNamed("BackToWorkoutButton", owner: self, options: nil)?.first as! UIView
xibView.frame = self.bounds
addSubview(xibView)
}
#IBAction func backButtonTapped(_ sender: UIButton) {
// THERE IS NIL ALWAYS
guard let routine = routine else { return print("Routine is nil") }
let routineVC = RoutineFactory.startWorkoutScene(routine: routine)
let navBarOnModal = UINavigationController(rootViewController: routineVC)
navBarOnModal.modalPresentationStyle = .fullScreen
guard let nav = UIApplication.shared.windows.last?.rootViewController else { return print("there is no nav")}
nav.present(navBarOnModal, animated: true, completion: nil)
self.removeFromSuperview()
}
}
Problem
I have an application that has a user registration view. It has many UITextField, and many of these have a picker with a toolbar embedded to close the picker i.e:
myTextField.inputView = myPicker
myTextField.inputAccessoryView = myToolbar
Essentially I want to reuse these text fields in different parts of my application, so I thought of subclassing UITextField, something like PickerUITextField.
Attempt
I've tried something like this:
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
self.resignFirstResponder()
}
}
Question
However, how can I detect in the view controller that the user has pressed the "Done" button of my PickerUITextField? In other words:
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
// I want this to be triggered whenever the country picker closes
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
Thank you for the help.
You can create a closure in PickerUITextField to perform done button action.
class PickerUITextField: UITextField {
let picker = UIPickerView()
let toolbar = UIToolbar()
var doneBtnAction:(() -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
private func setup() {
withToolbar()
self.inputView = self.picker
self.inputAccessoryView = self.toolbar
}
private func withToolbar() {
toolbar.barStyle = UIBarStyle.default
toolbar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done,
target: self, action: #selector(removeToolBar))
toolbar.setItems([space, doneButton], animated: false)
toolbar.isUserInteractionEnabled = true
toolbar.sizeToFit()
}
#objc func removeToolBar() {
doneBtnAction?()
self.resignFirstResponder()
}
}
And in your view controller, you can assign a closure. It will be called when you tap the done button.
class UserRegistrationViewController: UIViewController {
#IBOutlet weak var country: PickerUITextField!
override func viewDidLoad() {
super.viewDidLoad()
country.doneBtnAction = { [weak self] in
print("User selected \(self?.country.text!)")
}
}
}
You can use a protocol/delegate:
protocol PickerUITextFieldDelegate: class {
func didSelectCountry()
}
class PickerUITextField: UITextField {
// UITextField already have a 'delegate' we need a different name
weak var pickerDelegate: PickerUITextFieldDelegate?
let picker = UIPickerView()
let toolbar = UIToolbar()
#objc func removeToolBar() {
self.resignFirstResponder()
self.pickerDelegate?.didSelectCountry()
}
}
// We need to implement the PickerUITextFieldDelegate delegate here:
class UserRegistrationViewController: UIViewController, PickerUITextFieldDelegate {
#IBOutlet weak var country: PickerUITextField!
// Don't forget to set the delegate!
override func viewDidLoad() {
super.viewDidLoad()
self.country.pickerDelegate = self
}
// This will now be triggered by the delegate
func didSelectCountry() {
print("User selected \(country.text!)")
}
}
So Im trying to create a UIBarButtonItem with a custom UIView by subclassing it like so.
import UIKit
import SnapKit
class LocationManager: UIBarButtonItem {
let createdView = UIView()
lazy var currentCityLabel: UILabel = {
let currentCityLabel = UILabel()
currentCityLabel.text = "Philadelphia, PA"
guard let customFont = UIFont(name: "NoirPro-SemiBold", size: 20) else {
fatalError("""
Failed to load the "CustomFont-Light" font.
Make sure the font file is included in the project and the font name is spelled correctly.
"""
)
}
currentCityLabel.adjustsFontForContentSizeCategory = true
return currentCityLabel
}()
lazy var downArrow: UIImageView = {
let downArrow = UIImageView()
downArrow.contentMode = .scaleAspectFit
downArrow.image = UIImage(named: "downArrow")
return downArrow
}()
override init() {
super.init()
setupViews()
}
#objc func setupViews(){
customView = createdView
createdView.addSubview(currentCityLabel)
currentCityLabel.snp.makeConstraints { (make) in
make.left.equalTo(createdView.snp.left)
make.top.bottom.equalTo(createdView)
}
createdView.addSubview(downArrow)
downArrow.snp.makeConstraints { (make) in
make.left.equalTo(currentCityLabel.snp.right).offset(5)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
However, when I create it and assign it in my viewController I see nothing
import UIKit
class ViewController: UICollectionViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
}
#objc func setupViews(){
guard let collection = collectionView else {
return
}
collection.backgroundColor = .white
let customLeftBar = LocationManager()
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I've looked at other post and none seem to quite match my situation. I'm beginning to think it is because I didn't give the UIView a frame but I am not exactly sure how I would do that in this instance if that is the case. Anyone see anything I don't that could potentially help me solve this problem. Also setting a target doesn't work I tried two different ways and none of them triggers a thing
#objc func setupBarButtonItems(){
let customLeftBar = LocationManager()
customLeftBar.action = #selector(self.leftBarPressed)
customLeftBar.target = self
customLeftBar.customView?.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.leftBarPressed))
customLeftBar.customView?.addGestureRecognizer(tapGesture)
navigationItem.leftBarButtonItem = customLeftBar
}
#objc func leftBarPressed(){
print("left bar tapped")
}
Change your adding line from
self.navigationController?.navigationItem.leftBarButtonItem = customLeftBar
to
self.navigationItem.leftBarButtonItem = customLeftBar
When add the barItem, you need to add it via navigationItem of the ViewController, not NavigationController
EDITED for add the action
Your custom UIBarButtonItem is a Custom View's BarButtonItem, so the target and selector will not working.
You can add your custom action by adding a button into your customView, and send the action via closure
Init your closure
var didSelectItem: (() -> Void)?
Add the create button code in your #objc func setupViews()
let button = UIButton(type: .custom)
createdView.addSubview(button)
button.snp.makeConstraints { (maker) in
maker.top.bottom.leading.trailing.equalTo(createdView)
}
// button.backgroundColor = UIColor.cyan // uncomment this line for understand about the barbuttonitem's frame
button.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
and add the function
#objc func didTap(_ button: UIButton) {
print("Did tap button")
}
In your viewController, you can get the tap action by
customLeftBar.didSelectItem = { [weak self] in
self?.leftBarPressed()
}
Unfortunately, your barbuttonitem's default frame is 30x30, so you must be set the frame for your barbuttonitem. If not, you can only catch the tap action in 30x30 area (uncomment the code for see it)
I'm a beginner in IOS. I'm developping a Swift app and I am using a UISegmentedControl. It displays well in ios 11, but when I run my app on a IOS 10 device, the segmented control is not showing. Does anyone know why ?
Is the segmented control only available in IOS 11 ?
Here are the screenshots of my app (sorry I can't post images yet) :
IOS 11
IOS10
Here is my SegmentedViewController.swift :
import UIKit
import MMDrawerController
class SegmentedViewController: UIViewController {
#IBOutlet weak var viewContainer: UIView!
var segmentedController: UISegmentedControl!
var floorRequest:Int = 0
var segmentedControlIndex:Int = 0
lazy var travelViewController: TravelViewController = {
var viewController = self.initTravelViewController()
return viewController
}()
lazy var nearbyViewController: NearbyTableViewController = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
var viewController = storyboard.instantiateViewController(withIdentifier: "NearbyTableViewController") as! NearbyTableViewController
self.addViewControllerAsChildViewController(childViewController: viewController)
return viewController
}()
var views: [UIView]!
let appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
func initTravelViewController() -> TravelViewController {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
let viewController = storyboard.instantiateViewController(withIdentifier: "TravelViewController") as! TravelViewController
viewController.floorRequest = floorRequest
self.addViewControllerAsChildViewController(childViewController: viewController)
return viewController
}
override func viewDidLoad() {
super.viewDidLoad()
segmentedController = UISegmentedControl()
navigationItem.titleView = segmentedController
self.title = "TAB_BAR_MAP".localized()
}
override func viewWillAppear(_ animated: Bool) {
self.tabBarController?.navigationItem.title = "MENU_SECTION_TRAVEL".localized().uppercased()
// Navigation Bar
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName : UIColor.white, NSFontAttributeName: UIFont(name: "Lato-Bold", size: 18)!]
self.navigationController?.navigationBar.tintColor = .white
self.navigationController?.navigationBar.barTintColor = appDelegate.colorAqaDark
self.navigationController?.navigationBar.isTranslucent = false
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
if (self.navigationController?.viewControllers.count)! < 2 {
let buttonLeft: UIButton = appDelegate.aqaBarButton(image: #imageLiteral(resourceName: "IconWhiteMenu"))
buttonLeft.addTarget(self, action: #selector(toggleMenu), for: .touchUpInside)
buttonLeft.frame = CGRect.init(x: 0, y: 0, width: 25, height: 25)
let buttonMenu = UIBarButtonItem(customView: buttonLeft)
self.navigationItem.setLeftBarButton(buttonMenu, animated: false);
}
setupView()
super.viewWillAppear(animated)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func toggleMenu() {
appDelegate.mainContainer!.toggle(MMDrawerSide.left, animated: true, completion: nil)
}
private func setupView(){
setupSegmentedControl()
updateView()
}
private func updateView(){
travelViewController.view.isHidden = !(segmentedController.selectedSegmentIndex == 0)
nearbyViewController.view.isHidden = (segmentedController.selectedSegmentIndex == 0)
segmentedControlIndex = segmentedController.selectedSegmentIndex
}
private func setupSegmentedControl(){
segmentedController.removeAllSegments()
segmentedController.insertSegment(withTitle: "TAB_BAR_MAP".localized(), at: 0, animated: false)
segmentedController.insertSegment(withTitle: "TAB_BAR_NEARBY".localized(), at: 1, animated: false)
segmentedController.addTarget(self, action: #selector(selectionDidChange(sender:)), for: .valueChanged)
segmentedController.selectedSegmentIndex = segmentedControlIndex
}
func selectionDidChange(sender: UISegmentedControl){
updateView()
}
private func addViewControllerAsChildViewController(childViewController: UIViewController){
addChildViewController(childViewController)
view.addSubview(childViewController.view)
childViewController.view.frame = view.bounds
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParentViewController: self)
}
}
The problem is that you are not giving the segmented control any size. In iOS 11 the title view is sized internally by autolayout, but not in iOS 10 or before. So you end up with a segmented control of zero size.
I have 1 MainViewController from storyboard and 1 ModalUIView from xib.
In ModalUIView has present function for displaying modal and dismiss function for closing modal.
Step:
MainViewController -> OpenModal -> ModalUIView -> CloseModal
Here are my code:
UIViewUtil.swift
import Foundation
import UIKit
extension UIView {
// Load xib as the same name of CustomView that want to use xib
func loadXib() -> UIView{
let bundle = Bundle(for: type(of: self))
let nibName = type(of: self).description().components(separatedBy: ".").last!
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as! UIView
}
}
MainViewController.swift is subclass of UIViewController
#IBAction func guideButton(_ sender: Any) {
let modal = ModalUIView()
modal.present(targetView: self.view)
}
ModalUIView.swift is subclass of UIView
var view : UIView?
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
setup()
}
func setup() {
view = loadXib()
}
func present(targetView: UIView) {
view!.layer.cornerRadius = 10.0
view!.clipsToBounds = true
targetView.addSubview(view!)
// Set size
let popupWidth: CGFloat = targetView.frame.width - (targetView.frame.width * 0.04)
let popupHeight: CGFloat = targetView.frame.height - (targetView.frame.height * 0.08)
view!.frame = CGRect(x: targetView.frame.origin.x, y: targetView.frame.origin.y,
width: popupWidth, height: popupHeight)
view!.center = targetView.center
view!.transform = CGAffineTransform.init(scaleX: 1.3, y: 1.3)
view!.alpha = 0
UIView.animate(withDuration: 0.4){
self.view!.alpha = 1
self.view!.transform = CGAffineTransform.identity
}
}
#objc func dismiss(sender: UIButton!) {
print("dismiss")
}
My problem is mainViewController when I called present of modalUIView and then I tab on closeButton in modalUIView is not fired the action
I try with #IBAction but it not work:
#IBAction func CloseButtonAction(_ sender: UIButton) {
}
I also try with manual add action by programmatically but it not work too:
let closeButton: UIButton? = view?.viewWithTag(10) as! UIButton
closeButton!.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
Note:
I can see and tap on closeButton on modal.
I already add ModalUIView to xib File's Owner
OK, I have a solution now but not sure that is the best answer.
My solution is to add an action to closeButton of modalUIView via mainViewController not in modalUIView
If anyone have another best solution than this please suggest me.
You can also get an action in your viewcontroller following.
yourSubView.button.addTarget(self, action: #selector(buttonPress(sender:)), for: .touchUpInside)
and it will call your method.
func buttonPress(sender:UIButton){
print("Button pressed")
}