Add action to button in .xib file Using Swift(4.0) - swift

I implemented horizontal(Paging) slide show of views, created separate xib and integrated with the view controller. Slide show is working as expected but I want add action to the every views so that from there I can move directly to the respective main content pages. Find the below code for implementation of slide show.
func createSlides() -> [Slide] {
let slide1:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.labelTitle.text = "Page1"
let slide2:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide2.labelTitle.text = "Page2"
return [slide1, slide2]
Slide Function
func setupSlideScrollView(slides : [Slide]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
Xib file
class Slide: UIView {
#IBOutlet weak var labelTitle: UILabel!
var onClickCallback: (() -> Void)?
override init (frame : CGRect) {
super.init(frame : frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}

If I think what you are asking is right then do this. (BTW I am a bit rusty at this so please don't crucify me if I'm wrong). Also I don't know where you are designing this so I am going to assume it is in a different place to main.viewcontroller. If it isn't just add a button and create a segue to the content page.
Make a public var in createSlides Func and set it to the slide you are on. Eg: Slide 1. When slide two is opened it should update the var two slide two. For the sake of this we will call the var test
Add a button into your design file
Add a #IBAction into the viewcontroller. This #IBAction should be linked with your button.
inside this #IBAction create a segue to the content using the variable we created before. self.performSegue(withIdentifier: test, sender: self)
In the main view controller add two segues from the view with the slidshow to the view controller with the content (Assuming you only have two slides). One segue should be called slide 1 (The same name as the let you created in createSlides func.) The second should be called slide 2 (for obvious reasons.)
Now when your button in slide 1 is pressed it should perform the segue slide 1 and show your main content
Hope I helped, Toby.
Also if this doesn't work, is not what you want to achieve or is badly worded please comment what it is and I will try to fix it.

call this function and pass pageIndex where you want to jump:
func jumpToView(_ index : Int) {
let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0)
scrollView.setContentOffset(offset, animated: true)
}
Hope this what you are looking.
First line in function let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0), for calculate position where you want to move in scrollview. E.g : for first xib position will be CGPoint(x:0,y:0) and for 3rd xib, position will be CGPoint(x:200,y:0), consider each xib width is 100
setContentOffset use to scroll at specific location/point.
For more check Link

Related

Identifying Objects in Firebase PreBuilt UI in Swift

FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.

Set position of nspopover

I have a view controller (A), which will show another viewcontroller (B) as a popover.
In my VC (A) is an NSButton with this IBAction:
self.presentViewController(vcPopover, asPopoverRelativeTo: myButton.bounds, of: myButton, preferredEdge: .maxX, behavior: .semitransient)
The result:
now I would like to change the position of my popover - I would like to move it up.
I tried this:
let position = NSRect(origin: CGPoint(x: 100.0, y: 120.0), size: CGSize(width: 0.0, height: 0.0))
self.presentViewController(vcPopover, asPopoverRelativeTo: position, of: myButton, preferredEdge: .maxX, behavior: .semitransient)
But the position does not change
ANOTHER EXAMPLE
I have a segmented control. If you click on segment "1" a popover will be shown (same code like above). But the arrow pointed to segment "2" instead to segment "1"
First, ensure your popover is really an NSPopover and not simply an NSViewController. Assuming the view controller you want to wrap in the popover has a storyboard id of "vcPopover", getting the content vc would look like:
let popoverContentController = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil).instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "vcPopover")) as! NSViewController
Then, wrap it in a popover:
let popover = NSPopover()
popover.contentSize = NSSize(width: 200, height: 200) // Or whatever size you want, perhaps based on the size of the content controller
popover.behavior = .semitransient
popover.animates = true
popover.contentViewController = popoverContentController
Then, to present, call show(relativeTo:of:preferredEdge:):
vcPopover.show(relativeTo: myButton.bounds, of: myButton, preferredEdge: .maxX)
This should update the position of the popover.
Update: You are likely using an NSSegmentedControl, which means you need to pay special attention to the rect you pass in show. You need to pass a bounds rect within the segmented control's coordinate system that describes the area of the segment. Here's a detailed example:
// The view controller doing the presenting
class ViewController: NSViewController {
...
var presentedPopover: NSPopover?
#IBAction func selectionChanged(_ sender: NSSegmentedControl) {
let segment = sender.selectedSegment
if let storyboard = storyboard {
let contentVC = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("vcPopover")) as! NSViewController
presentedPopover = NSPopover()
presentedPopover?.contentSize = NSSize(width: 200, height: 200)
presentedPopover?.behavior = .semitransient
presentedPopover?.animates = true
presentedPopover?.contentViewController = contentVC
}
presentedPopover?.show(relativeTo: sender.relativeBounds(forSegment: segment), of: sender, preferredEdge: .minY)
}
}
extension NSSegmentedControl {
func relativeBounds(forSegment index: Int) -> NSRect {
// Assuming equal widths
let segmentWidth = bounds.width / CGFloat(segmentCount)
var rect = bounds
rect.size.width = segmentWidth
rect.origin.x = rect.origin.x + segmentWidth * CGFloat(index)
return rect
}
}
Notice that the extension to NSSegmentedControl calculates an approximate rectangle for the segment using the width. This method assumes equal widths and does not account for borders. You may modify this method to account for what you need. Information about getting the frame of a segment for iOS (which is similar) can be found here.
This example is verified as working correctly as long as a view controller exists in the same storyboard with a storyboard identifier of "vcPopover".

Close partially shown modal view controller by tapping outside of the view controller

I made a custom modal transition by using UIViewControllerAnimatedTransitioning. After the transition has completed, a modally shown view controller fills part of parent view that some part of parent view controller is still visible.
I need to know how to detect tap on outside of shown view controller(or partially shown parent view controller) so I can use it to close the modal. I understand how to add gesture to a view but I cannot find which view to add a gesture to.
I looked through stackoverflow but there aren't example specifically for custom modal transition.
Here is a code for custom transition
class PartialSlideInAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
var direction: SlideAnimationDirectionStyle
var duration: TimeInterval = 0.125
var presentedControllerWidth: CGFloat = 250
let fadeAlpha: CGFloat = 0.5
init(direction: SlideAnimationDirectionStyle) {
self.direction = direction
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return TimeInterval(duration)
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// Setup Views
guard let presentedFromVC = transitionContext.viewController(forKey: .from) else {transitionContext.completeTransition(false); return }
guard let presentingToVC = transitionContext.viewController(forKey: .to) else {transitionContext.completeTransition(false); return }
let contrainerView = transitionContext.containerView // アニメーションはこのViewで行われる、アニメーションを実装する presenter, presenteeをこの上に載せないとアニメーションが動かない
// Setup Frames
let positionHidden = CGRect(x: contrainerView.frame.width + 1, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
let positionShown = CGRect(x: contrainerView.frame.width - presentedControllerWidth, y: 0, width: presentedControllerWidth, height: contrainerView.frame.height)
switch direction {
case .slideIn:
contrainerView.addSubview(presentingToVC.view)
presentingToVC.view.frame = positionHidden
UIView.animate(withDuration: duration, animations: {
presentingToVC.view.frame = positionShown
presentedFromVC.view.alpha = self.fadeAlpha
}, completion: { success in
transitionContext.completeTransition( success )
})
case .slideOut:
contrainerView.addSubview(presentedFromVC.view)
presentedFromVC.view.frame = positionShown
UIView.animate(withDuration: duration, animations: {
presentedFromVC.view.frame = positionHidden
presentingToVC.view.alpha = 1.0
}, completion: { success in
transitionContext.completeTransition( success )
})
}
}
}
Have you tried using UIGestureRecognizer yet? The UIGestureRecognizer can be utilized to recognize tap events, both inside a child view controller and a parent view controller. For swift 3, there should be a handleTapBehind function of the UIRecognizerDelegate that should allow you to do what you're looking for.
See if the what is documented here
See the "Swift 3.1 solution that works in both portrait and landscape" in the linked post.

How to add a target to a button added to a View? Detail: View is controlled variable within a Class

I am organizing a code an app that iPad Air 2 I use to control finances of a company (as a developer without AppStore). This app displays painels with information, and these panels are displayed and hidden as the user action.
I decided to try to create different ways and separate my schedule between arquivos.swift.
That's my ViewController. Only two buttons, it's all I do for Interface Builder (for example). The rest of the application is created programmatically.
This ViewController is connected directly to the ViewController class:
class ViewController: UIViewController {
override func prefersStatusBarHidden() -> Bool {
return true
}
var panelInfo: PanelInfo!
var selfSize: CGSize {
get {
return self.view.frame.size
}
}
override func viewDidLoad() {
super.viewDidLoad()
panelInfo = PanelInfo(viewControllerSize: selfSize)
self.view.addSubview(panelInfo.panel)
}
#IBAction func showPropNavigationbar(sender: AnyObject) {
panelInfo.show()
}
#IBAction func hidePropNavigationbar(sender: AnyObject) {
panelInfo.hide()
}
}
To create a view that will be displayed where the information, I created a class that manages it.
class PanelInfo {
/*** View Controller Size ***/
private var viewSize: CGSize!
/*** View ***/
internal var panel: UIView!
/*** View Panel Size ***/
private var size: CGSize!
/*** View Position ***/
private var originShow: CGPoint!
private var originHide: CGPoint!
private var closeButton: UIButton!
/*** Start the instance ***/
init(viewControllerSize: CGSize) {
/*** Set values in variables above ***/
viewSize = viewControllerSize
size = CGSize(width: 300, height: viewSize.height)
originShow = CGPoint(x: viewSize.width - size.width, y: 0)
originHide = CGPoint(x: viewSize.width, y: 0)
/*** Create the panel view ***/
panel = UIView(frame: CGRect(origin: originHide, size: self.size))
panel.backgroundColor = UIColor(red: 0.014, green: 0.106, blue: 0.179, alpha: 1.000)
/*** Create the closeButton ***/
closeButton = UIButton(type: .System)
closeButton.frame = CGRect(origin: CGPoint(x: 8, y: 8), size: CGSize(width: 32, height: 32))
closeButton.setImage(UIImage(named: "closeButton"), forState: .Normal)
panel.addSubview(closeButton)
}
func hidepanel(sender: AnyObject) {
print(sender)
self.hide()
}
/*** Show panel in ViewController ***/
internal func show() -> Void {
UIView.animateWithDuration(0.3) { () -> Void in
self.panel.frame.origin = self.originShow
}
}
/*** Hide panel in ViewController ***/
internal func hide() -> Void {
UIView.animateWithDuration(0.3) { () -> Void in
self.panel.frame.origin = self.originHide
}
}
}
When the code is executed, and I touch in 'Show', the panel appears. Working perfectly.
When I touch in 'Hide', the panel disappears. Working perfectly.
The Problem
I'm trying to create a button for programming and add a target to run make the panel close. This button is closeButton.
If i added the target (nil, action: "hidepanel:", forControlEvents: .TouchUpInside
Nothing happens
If I change for closeButton.addTarget(nil, action: "hidepanel:", forControlEvents: .TouchUpInside
Nothing happens
If I change for closeButton.addTarget(self, action: "hidepanel:", forControlEvents: .TouchUpInside)
The error appear:
*** NSForwarding: warning: object 0x7a99ee00 of class 'Panel_Model.PanelInfo' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[Panel_Model.PanelInfo hide panel:]
If I try change for closeButton.addTarget(self, action: "hidepanel", forControlEvents: .TouchUpInside)
The previous error appears
Note: This is a example. The app original is bigger. When I started to build the app, had little time to do it and had little experience in Swift (and Obj-C as well), so I created everything needed in a ViewController. Today I am with almost 5000 lines of programming, and need to find a better way to organize.
I believe that the error can be related to (target: AnyObject?, ...) , the first item of AddTarget, for the reason of being used in a class without superclass... But I can't find a way to solve it.
My question:
1º This is the best form of organization?
2º Knowing that I am using a class, and this class have a variabel controlling a UIView. Can I implement added buttons to view and set targets on the buttons?
3º If possible, what is the best way to do this, could show examples?
Try the following
let button:UIButton = UIButton(frame: CGRect(x: 0, y: 0, width: 70, height: 30))
button.addTarget(self, action: Selector("hidepanel:"), forControlEvents: UIControlEvents.TouchUpInside)
Update
Try inheriting PanelInfo from `NSObject.

Can I add a UIImageView to an UIInputViewController in Swift?

I'm working on a custom keyboard and want to add some UIImageViews to the interface. I can load a .Xib fine as shown below, but when I try to create a UIImage within the main view it causes it to crash. Do you know why this is?
import UIKit
class KeyboardViewController: UIInputViewController {
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
let myView = NSBundle.mainBundle().loadNibNamed("KeyboardView", owner: nil, options: nil)[0] as UIView
self.view.addSubview(myView)
let image = UIImage(named: "grid.png")
let imageSize = CGSize(width: 216, height: 216)
var gridImageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize))
gridImageView.image = image
//tried this- Crashes
self.addSubview(gridImageView)
//also tried this- Crashes
self.view.insertSubview(view: gridImageView, aboveSubview: myView)
}
If I manually add a ImageView to the .XIB, it also crashes. I feel like UIInputViewController will not let you use a Image View, even though the documentation would indicate it's possible. I'm usually an idiot with these things so am probably misunderstanding the docs:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/inputView