I'm very new to swift so even this is a bit complicated to me at this point; sorry for sounding dumb if I do
Preface:
I have a main view controller(lets call it viewA) and a UIView which gets its functionality from a .xib file, let's call this viewB
This is all in the single main view controller page
The problem:
So i want my ViewController to execute a bunch of methods in sequence one of which is to call the result from a function in this viewB(its a subview so i cant use segues)
So in the function i want to return the result only when either--
A button to be pressed
30 seconds have passed
Whats the most efficient way to tackle this problem?
EDIT:
In a nutshell i want to make my main queue execution wait till there is an input from the player or 30 seconds have passed
Code structure:
ViewController:
class ViewController{
var viewB:CustomView
//methods
function to execute{
viewB.executeFunction()
}
}
CustomView:
class CustomView:UIView{
//functions of initializing buttons and text boxes
func executeFunction(){
//wait for a user input to complete then return from this function. i cant figure out how this works
}
}
Image of the UI idea
I think Option 1 is more suitable for your scenario. You can call the block when the button in your nib is pressed. Following is the code how you can use block in swift:
// Code for your UIViewController
func presentPopup() {
let popup = NibClassName.instantiateViewFromNib()
popup.btnPressBlock = { [unowned self] (success) -> () in
// Code after button of UIView pressed
}
popup.frame = CGRect(x: 100, y: 100, width: 300, height: 300)
self.view.addSubview(popup)
}
// Code of your UIView class file
var btnPressBlock: ((Bool) -> ())?
class func instantiateViewFromNib() -> NibClassName {
let obj = Bundle.main.loadNibNamed("NibName", owner: nil, options: nil)![0] as! NibClassName
return obj
}
#IBAction func btnPressed(sender: UIButton) {
btnPressBlock!(true)
removeFromSuperview()
}
Related
I have the following code for a button in my application to test my implementation of NVActivityIndicatorView:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
sleep(2)
self.stopAnimatingActivityIndicator()
}
The view controllers in my application also have this extension:
extension UIViewController: NVActivityIndicatorViewable {
func startAnimatingActivityIndicator() {
let width = self.view.bounds.width / 3
let height = width
let size = CGSize(width: width, height: height)
startAnimating(size, message: "Loading...", type: NVActivityIndicatorType.circleStrokeSpin)
}
func stopAnimatingActivityIndicator() {
self.stopAnimating()
}
}
The loading animations work elsewhere in the same view controller (i.e., the viewDidLoad() function) but for some reason I'm unable to get the loading animation to work on this button. The button is connected correctly as the application does sleep for the appropriate amount of time, but the loading animations fail to run.
Thanks in advance for the help!
The Indicator won't spin because the main thread is asleep. Use a 2 second timer to turn off the spinning instead.
#FryAnEgg coming in with the solution! The sleep(2) was preventing the loading animations from running.
Here's my updated code:
#IBAction func goButtonPressed(_ sender: Any) {
self.startAnimatingActivityIndicator()
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
self.stopAnimatingActivityIndicator()
}
}
I'm working on a simple game in my free time which has two view controllers: the default ViewController, and GameViewController. I have it setup so that GameViewController is a subview of a UIView within my ViewController, which I did so that I could customize transitions between my main menu and the actual game (I would love to find some more efficient ways of doing this in the future!)
The problem I'm currently having, is that selectors which I declare in my GameViewController class won't call functions within the GameViewController class, but, selectors do call functions within my ViewController class just fine. For example, here's some code in my ViewController class and GameViewController classes:
class ViewController: UIViewController {
#objc func myFunc() {
print("Some output to show that the ViewController function is working")
}
}
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myButton = UIButton()
myButton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
myButton.backgroundColor = .green
// This works fine. The function in ViewController is called, and I get some output in the console
myButton.addTarget(self, action: #selector(ViewController.myFunc), for:.touchUpInside)
// This does NOT work. No output is shown, therefore the function in GameViewController never got called
myButton.addTarget(self, action: #selector(self.myFunc2), for:.touchUpInside)
view.addSubview(myButton)
}
#objc func myFunc2() {
print("Some different output to show that the GameViewController function is working")
}
}
I also tried, just in case there was some problem with my button, to use Notifications to call my function. Since I knew my function in ViewController was working, I posted a notification from that function and added an observer to my GameViewController:
class ViewController: UIViewController {
#objc func myFunc() {
print("Some output to show that the ViewController function is working")
NotificationCenter.default.post(name: .myNotification, object: nil)
}
}
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myButton = UIButton()
myButton.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
myButton.backgroundColor = .green
myButton.addTarget(self, action: #selector(ViewController.myFunc), for:.touchUpInside)
view.addSubview(myButton)
NotificationCenter.default.addObserver(self, selector: #selector(self.myFunc2(notification:)), name: .myNotification, object: nil)
}
#objc func myFunc2(notification: NSNotification) {
print("Some different output to show that the GameViewController function is working")
}
}
extension Notification.Name {
static let myNotification = Notification.Name("myNotification")
}
And even using Notifications, the function was called in ViewController, the notification was (supposedly) posted, and still my GameViewController function never got called.
I've used breakpoints to make sure 100% that my observer is being called before the notification is posted, and I've called myFunc2 in GameViewController's viewDidLoad and got output, so I really can't understand why I'm having so much trouble.
Any insights help, thanks!
Well, I made it work.
It turns out that, and I'm honestly not 100% sure this is totally accurate, because I was using a convenience init (something I forgot to mention in the OP), "self" ended up pointing to the current instance of ViewController and so when I tried to call "self.myFunc2," it was actually trying to call "ViewController.myFunc2" which obviously didn't exist.
It doesn't make sense to my why it wasn't outright crashing when it tried to call a nonexistent function, and I don't really get why "self" wasn't working as intended, but I guess that's a case closed.
This might be a weird question, but i'm trying to code like a pro which obviously i am not.
Right now i have an extension which uses UIView and my concept is making it like an alert
For example, i coded the following:
extension UIView {
typealias completionHandler = (_ success:Bool) -> Void
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
public func showLuna(title messageTitle:String, message messageDescription:String, dissmiss dissmissDuration: TimeInterval) {
let luna = UIView()
luna.frame = CGRect(x: 16, y: 30, width: screenWidth - 30, height: 60)
luna.center.x = self.center.x
luna.backgroundColor = .white
luna.addShadow(radius: 11, opacity: 0.2)
luna.layer.cornerRadius = 10
}
}
And on my other ViewController, I use this to present Luna
#IBAction func presentLuna(_ sender: Any) {
self.view.showLuna(title: "Oooh", message: "Oops, something went horribly wrong!", dissmiss: 2.5);
}
At this very specific moment, I've been digging StackOverFlow for a day to find an answer. How do i attach a gesture recognizer or a function WITH a code block so the user can perform another task when luna gets tapped, or is that even possible with Extensions??
This is maybe what #rmaddy means by subclassing UIView:
First, create a subview so you can use it to respond to touch events:
class LunaView: UIView {
typealias LunaViewCompletionBlock = (_ isSuccessful: Bool) -> Void
var label: UILabel
//other things you need
var completionHandler: LunaViewCompletionBlock?
init(frame: CGRect, /* other properties such as title and colour */, completionHandler: LunaViewCompletionBlock?) {
self.completionHandler = completionHandler
self.label = UILabel()
super.init(frame: frame)
isUserInteractionEnabled = true
//this is how we handle touch
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTouch)))
addSubview(label)
// set up frame or constraints(if you use autolayout) for label and other views
// configure label.text and other things
}
func handleTouch() {
completionHandler(/*true or false*/)
}
/*other methods needed in this class*/
}
Then, to use it, simply do this in the extension:
extension UIView {
private var screenWidth: CGFloat {
return UIScreen.main.bounds.width
}
public func showLuna(title messageTitle:String, message messageDescription:String, dissmiss dissmissDuration: TimeInterval, completionHandler: LunaView.LunaViewCompletionBlock) {
let luna = LunaView(frame: /*size*/, /*other things like title*/, completionHandler: completionHandler)
// set luna's center, shadow, auto-dismiss time, colour.....
}
}
Answering your question:
How do i attach a gesture recognizer or a function WITH a code block so the user can perform another task when luna gets tapped, or is that even possible with Extensions??
You can't detect a touch event on just a general UIView you need a UIControl. Since the UIControl type inherits from UIView you will be able to do any of your typical drawing or view hierarchy stuff but you can also setup touch actions and callbacks using addTarget(:action:for:) or other UIControl mechanisms.
I want to notify users that an action has been completed in the background. Currently, the AppDelegate receives notification of this:
func didRecieveAPIResults(originalRequest: String, apiResponse: APIResponse) {
if(originalRequest == "actionName") {
// do something
}
}
I'd really like to display a pop over notification (e.g. "Awarded points to 10 students") over the currently active view.
I know how to do this with NSNotification, but that means I have to add a listener to each of the views. An alternative to that would be great!
The next part of question is how do I actually get the view to fade in and then fade out again in front of whatever view I have - be that a table view, collection view or whatever else. I've tried the following code (in the viewDidLoad for the sake of testing):
override func viewDidLoad() {
// set up views
let frame = CGRectMake(0, 200, 320, 200)
let notificationView = UIView(frame: frame)
notificationView.backgroundColor = UIColor.blackColor()
let label = UILabel()
label.text = "Hello World"
label.tintColor = UIColor.whiteColor()
// add the label to the notification
notificationView.addSubview(label)
// add the notification to the main view
self.view.addSubview(notificationView)
print("Notification should be showing")
// animate out again
UIView.animateWithDuration(5) { () -> Void in
notificationView.hidden = true
print("Notification should be hidden")
}
}
The view does appear without the hiding animation, but with that code in it hides straight away. I'm also not sure how to stick this to the bottom of the view, although perhaps that's better saved for another question. I assume I'm doing a few things wrong here, so any advice pointing me in the right direction would be great! Thanks!
For your notification issue, maybe UIAlertController suits your needs?
This would also solve your issues with fading in/out a UIView
func didRecieveAPIResults(originalRequest: String, apiResponse: APIResponse) {
if(originalRequest == "actionName") {
// Creates an UIAlertController ready for presentation
let alert = UIAlertController(title: "Score!", message: "Awarded points to 10 students", preferredStyle: UIAlertControllerStyle.Alert)
// Adds the ability to close the alert using the dismissViewControllerAnimated
alert.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Cancel, handler: { action in alert.dismissViewControllerAnimated(true, completion: nil)}))
// Presents the alert on top of the current rootViewController
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
}
UIAlertController
When adding a subview you want to be on top of everything else, do this:
self.view.addSubview(notificationView)
self.view.bringSubviewToFront(notificationView)
Fading a UIView by changing the alpha directly:
For testing, you should be calling this in your viewDidAppear so that the fading animation starts after the view actually is shown.
// Hides the view
UIView.animateWithDuration(5) { () -> Void in
notificationView.alpha = 0
}
// Displays the view
UIView.animateWithDuration(5) { () -> Void in
notificationView.alpha = 0
}
This solution takes up unnecessary space in your code, I would recommend extensions for this purpose.
Extensions:
Create a Extensions.swift file and place the following code in it.
Usage: myView.fadeIn(), myView.fadeOut()
import UIKit
extension UIView {
// Sets the alpha to 0 over a time period of 0.15 seconds
func fadeOut(){
UIView.animateWithDuration(0.15, animations: {
self.alpha = 0
})
}
// Sets the alpha to 1 over a time period of 0.15 seconds
func fadeIn(){
UIView.animateWithDuration(0.15, animations: {
self.alpha = 1
})
}
}
Swift 2.1 Extensions
Hope this helps! :)
I have a custom UIView with a UITapGestureRecognizer attached to it. The gesture recognizer calls a method called hide() to remove the view from the superview as such:
func hide(sender:UITapGestureRecognizer){
if let customView = sender.view as? UICustomView{
customView.removeFromSuperview()
}
}
The UICustomView also has a show() method that adds it as a subview, as such:
func show(){
// Get the top view controller
let rootViewController: UIViewController = UIApplication.sharedApplication().windows[0].rootViewController!!
// Add self to it as a subview
rootViewController.view.addSubview(self)
}
Which means that I can create a UICustomView and display it as such:
let testView = UICustomView(frame:frame)
testView.show() // The view appears on the screen as it should and disappears when tapped
Now, I want to turn my show() method into a method with a completion block that is called when the hide() function is triggered. Something like:
testView.show(){ success in
println(success) // The view has been hidden
}
But to do so I would have to call the completion handler of the show() method from my hide() method.
Is this possible or am I overlooking something?
Since you are implementing the UICustomView, all you need to do is store the 'completion handler' as part of the UICustomView class. Then you call the handler when hide() is invoked.
class UICustomView : UIView {
var onHide: ((Bool) -> ())?
func show (onHide: (Bool) -> ()) {
self.onHide = onHide
let rootViewController: UIViewController = ...
rootViewController.view.addSubview(self)
}
func hide (sender:UITapGestureRecognizer){
if let customView = sender.view as? UICustomView{
customView.removeFromSuperview()
customView.onHide?(true)
}
}
Of course, every UIView has a lifecycle: viewDidAppear, viewDidDisappear, etc. As your UICustomView is a subclass of UIView you could override one of the lifecycle methods:
class UICustomView : UIView {
// ...
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear (animated)
onHide?(true)
}
}
You might consider this second approach if the view might disappear w/o a call to hide() but you still want onHide to run.