I am trying to setup a success/error view on a controller via protocol and extensions.
What I want to achieve is that I want to get into the state where it is enough to implement the protocol on a controller, and from there get access to the successView (no additional boilerplate).
This is what I have so far:
protocol SucessViewProtocol where Self: UIViewController {
func initSuccessView()
var successView: UIView! { get set }
var topConstraint: NSLayoutConstraint! { set get }
func showSuccess()
func hideSucess()
}
extension SucessViewProtocol {
func showSuccess() {
//animate displaying success message
}
func hideSucess() {
//animate hiding success message
}
func initSuccessView() {
successView = UIView()
topConstraint = NSLayoutConstraint()
// init success view and top constraint
}
}
Now when I implement the protocol on the controller it looks like this:
// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
var successView: UIView! {
get {
//getter
}
set {
//setter
}
}
var topConstraint: NSLayoutConstraint! {
get {
//getter
}
set {
//setter
}
}
}
I guess my problem is obvious because I get the successView and topConstraint as properties inside my controller that is implementing the SucessViewProtocol. I am initializing the properties from the protocol inside the extension, so what I need would be just an access to these properties (not declaring them again in my controller). I guess I am missing some "glue" part between the protocol - extension - controller
I want to be able to implement the protocol on a controller, call initSuccessView() and from there it should just be enough to call showSuccess and hideSuccess.
Edit:
This is how I want to use this construct:
class ConsumingViewController: UIViewController {
func viewDidLoad() {
initSuccessView()
loadData()
}
private func loadData() {
//successfullyloaded
showSuccess()
}
}
// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
var successView: UIView! {
get {
//getter
}
set {
//setter
}
} *PROBLEMATIC*
var topConstraint: NSLayoutConstraint! {
get {
//getter
}
set {
//setter
}
} *PROBLEMATIC*
}
As I said, the problem is that the properties successView and topConstraing are being redeclared inside ConsumingViewController (because they are part of the protocol). I would need to actually not be visibile inside the controller, but just being used inside the extension. But then there is the problem with stored properties inside extensions ...
May be you want this?
protocol SucessViewProtocol {
func showSuccess()
func hideSucess()
}
fileprivate struct Key {
static var runtimeKey: Int = 0
}
extension SucessViewProtocol where Self: UIViewController {
var successView: UIView? {
get {
return objc_getAssociatedObject(self, &Key.runtimeKey) as? UIView
}
set {
objc_setAssociatedObject(self, &Key.runtimeKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
func showSuccess() {
successView = UIView()
//animate displaying success message
view.addSubview(successView)
}
func hideSucess() {
//animate hiding success message
successView?.removeFromSuperview()
}
}
add protocol variables inside main scope not extension scope UIViewController like this.
public class ViewController: UIViewController, SucessViewProtocol {
var successView: UIView!
var topConstraint: NSLayoutConstraint!
}
By doing this you do not need to define getter and setter for properties
and inside ViewDidLoaded you can initSuccessView:
public override func viewDidLoad() {
super.viewDidLoad()
self.initSuccessView()
}
and call custom function:
func show() {
self.showSuccess()
}
func hide() {
self.hideSucess()
}
Solution = Optional Porotocols
You just need add properties to the extension as well to make them optional.
protocol SucessViewProtocol where Self: UIViewController {
func initSuccessView()
var successView: UIView! { get set }
var topConstraint: NSLayoutConstraint! { set get }
func showSuccess()
func hideSucess()
}
extension SucessViewProtocol {
// 👇🏼------ these 2 properties added ------👇🏼
var successView: UIView! { get{ nil } set{} }
var topConstraint: NSLayoutConstraint! { get{ nil } set{} }
func showSuccess() {}
func hideSucess() {}
func initSuccessView() {
successView = UIView()
topConstraint = NSLayoutConstraint()
}
}
Then, when you conform SucessViewProtocol on ConsumingViewController you won't require to implement protperties.
// MARK: SuccessView
extension ConsumingViewController: SucessViewProtocol {
// There's NO compiler error here!
}
Related
I want to trigger Navigation controller to some other screen when i press the button in UIView class. How can i do this?
//Code for UIView Class in Which Button Iboutlet is created
import UIKit
protocol ButtonDelegate: class {
func buttonTapped()
}
class SlidesVC: UIView {
var delegate: ButtonDelegate?
#IBAction func onClickFinish(_ sender: UIButton) {
delegate?.buttonTapped()
}
#IBOutlet weak var imgProfile: UIImageView!
}
//ViewController Class code in Which Button Protocol will be entertained
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
var slidesVC = SlidesVC()
override func viewDidLoad() {
super.viewDidLoad()
slidesVC = SlidesVC()
// add as subview, setup constraints etc
slidesVC.delegate = self
}
extension BaseVC: ButtonDelegate {
func buttonTapped() {
self.navigationController?.pushViewController(SettingsVC.settingsVC(),
animated: true)
}
}
A more easy way is to use typealias. You have to write code in 2 places. 1. your viewClass and 2. in your View Controller.
in your SlidesView class add a typealias and define param type if you need otherwise leave it empty.
class SlidesView: UIView {
typealias OnTapInviteContact = () -> Void
var onTapinviteContact: OnTapInviteContact?
#IBAction func buttonWasTapped(_ sender: UIButton) {
if self.onTapinviteContact != nil {
self.onTapinviteContact()
}
}
}
class SwipingMenuVC: BaseVC, UIScrollViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let slidesView = SlidesView()
slidesView.onTapinviteContact = { () in
// do whatever you want to do on button tap
}
}
You can use the delegate pattern to tell the containing ViewController that the button was pressed and let it handle whatever is needed to do next, The view doesn't really need to know what happens.
A basic example:
protocol ButtonDelegate: class {
func buttonTapped()
}
class SomeView: UIView {
var delegate: ButtonDelegate?
#IBAction func buttonWasTapped(_ sender: UIButton) {
delegate?.buttonTapped()
}
}
class ViewController: UIViewController {
var someView: SomeView
override func viewDidLoad() {
someView = SomeView()
// add as subview, setup constraints etc
someView.delegate = self
}
}
extension ViewController: ButtonDelegate {
func buttonTapped() {
self.showSomeOtherViewController()
// or
let vc = NewViewController()
present(vc, animated: true)
}
}
I am using xlpagertabstrip and I have a parent view controller which has two children (child1, child2).
In my parent view controller, I show a UIActivityViewIndicator but I want to know how to hide that indicator in my child1.
This is my code:
ParentViewController:
override func viewDidLoad() {
showActivityIndicator()
super.viewDidLoad()
}
func showActivityIndicator() {
//code related to titleview
navigationItem.titleView = titleView
}
func hideActivityIndicator() {
navigationItem.titleView = nil
}
Child1ViewController:
override func viewDidLoad() {
super.viewDidLoad()
call_api()
}
func call_api(){
//code related to api
//if api is ok, I call hideActivityIndicator()
let pctrl = ParentViewController()
pctrl.hideActivityIndicator()
}
But that code does not work. How can I solve that?
Just pass hideActivityIndicator() from the parent to the child and call it when necessary. So whenever you create your child controller do this:
// Parent Controller
childVC.someMethodFromParent = hideActivityIndicator
And in your ChildController do this:
// Child Controller
internal var someProperty: (() -> Void)!
override func viewDidLoad() {
super.viewDidLoad()
call_api()
}
func call_api(){
//code related to api
//if api is ok, I call hideActivityIndicator()
someMethodFromParent()
}
This should work
How about having a ChildViewControllerDelegate? Something like:
class ParentViewController {
func someFunc(){
...
childVC.delegate = self
...
}
}
extension ParentViewController: ChildViewControllerDelegate {
func childViewControllerDidFinishApiCall() {
hideActivityIndicator()
}
}
protocol ChildViewControllerDelegate: class {
func childViewControllerDidFinishApiCall()
}
class ChildViewController {
weak var delegate: ChildViewControllerDelegate?
func call_api(){
//code related to api
let pctrl = ParentViewController()
delegate?.childViewControllerDidFinishApiCall()
}
}
I actually have a custom uiview that works, and I think everything in the uiview that doesn't work is set up the same as the one that works. Here is the code:
protocol SessionDisplayViewDelegate: class
{
func homeButtonTapped()
}
class SessionDisplayView: UIView
{
#IBOutlet var view: UIView!
#IBOutlet weak var accountImage: UIButton!
#IBOutlet weak var mySKView: SKView!
#IBOutlet weak var sessionTitle: UILabel!
weak var delegate: SessionDisplayViewDelegate?
required init(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)!
commonInitialization()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInitialization()
}
func commonInitialization()
{
Bundle.main.loadNibNamed("SessionDisplayView", owner: self, options: nil)
self.addSubview(self.view)
//accountImage = UIImage(data: ShareData.sharedInstance.accounts[ShareData.sharedInstance.indexOfCurrentAccount].picture!, scale: 1.0)
}
func onView()
{
let curScene = MyScene(size: mySKView.bounds.size)
curScene.scaleMode = SKSceneScaleMode.aspectFill
mySKView.presentScene(curScene)
let myImage = UIImage(data: (ShareData.sharedInstance.accounts[ShareData.sharedInstance.indexOfCurrentAccount]?.picture!)! as Data, scale: 0.5)
accountImage.setImage(myImage, for: UIControlState())
sessionTitle.text = ShareData.sharedInstance.accounts[ShareData.sharedInstance.indexOfCurrentAccount]?.name
var myTest = sessionTitle.text
self.view.setNeedsDisplay()
}
#IBAction func homeButtonTouched(_ sender: UIButton)
{
delegate?.homeButtonTapped()
}
}
I don't know that the self.view.setNeedsDisplay() needs to be called- I'm just trying to make it work. The title doesn't change even though the variable for the title does change- I've checked that, and that is working. Either there's a connection issue between the label and the variable, or the view controller isn't getting the update signal to change the view controller. I don't know which- and I don't know how to nail down which it is, either. Any ideas on how to fix this would be deeply appreciated. Here is the ViewController code:
class SessionDisplayViewController: UIViewController, SessionDisplayViewDelegate
{
#IBOutlet weak var mySessionView: SessionDisplayView!
func homeButtonTapped()
{
self.performSegue(withIdentifier: "ReturnHome", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
mySessionView.onView()
mySessionView.sessionTitle.text = "Test"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override var shouldAutorotate : Bool {
if (UIDevice.current.orientation == UIDeviceOrientation.landscapeLeft) || (UIDevice.current.orientation == UIDeviceOrientation.landscapeRight) || (UIDevice.current.orientation == UIDeviceOrientation.unknown)
{
return true
}
else
{
return false
}
}
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
return [UIInterfaceOrientationMask.portrait, UIInterfaceOrientationMask.portraitUpsideDown]
}
override var preferredStatusBarStyle : UIStatusBarStyle
{
return UIStatusBarStyle.lightContent
}
}
Any thoughts or suggestion on how to get the UIView to update would be most welcome. Thanks.
Sincerely,
Sean
How does you add your custom view to your root view? From code or from xib/storyboard? If you done that by xib/storyboard you must override awakeFromNib method in your custom view class and call commonInitialization inside it. init() method was call only if you create your view by code.
I'm trying to play a youtube video using
Swift-Youtube-Player
and I'm currently having trouble with the delegate methods not being called.
I have created an outlet from IB to this player, but I couldn't drag the delegate to the view controller from IB for some reason so I tried to do it in code.
This is what I tried:
#IBOutlet weak var youtubePlayer: YouTubePlayerView!
var _currentYoutubeId: String = "";
override func viewDidLoad() {
super.viewDidLoad()
print(_currentYoutubeId)
youtubePlayer.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func playerReady(videoPlayer: YouTubePlayerView) {
print(videoPlayer)
if youtubePlayer.ready {
youtubePlayer.playerVars = [
"playsinline": "1",
"controls": "0",
"showinfo": "0"
]
youtubePlayer.loadVideoID(_currentYoutubeId)
if youtubePlayer.playerState != YouTubePlayerState.Playing {
youtubePlayer.play()
} else {
youtubePlayer.pause()
}
}
}
func playerStateChanged(videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
print("promjena")
}
func playerQualityChanged(videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
print("promjena")
}
I'm doing this in the newest XCode versions and I'm very new to iOS, Swift and everything Apple related.
Concretely, my question is how to connect this delegate to the player so that its lifecycle methods are called?
EDIT: I have previously managed to this with UICollectionViewDelegate, but I haven't succeded with this custom view.
this is the way I use Swift-Youtube-Player.
class videoController: UIViewController, YouTubePlayerDelegate {
#IBOutlet var videoPlayer: YouTubePlayerView!
var videoID: String?
override func viewDidLoad() {
super.viewDidLoad()
videoPlayer.delegate = self
if let videoID = self.videoID {
videoPlayer.loadVideoID(videoID)
}
}
func playerReady(videoPlayer: YouTubePlayerView) {
videoPlayer.play()
}
func playerStateChanged(videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
if videoPlayer.playerState == YouTubePlayerState.Ended || videoPlayer.playerState == YouTubePlayerState.Paused {
//Your code here
}
}
func playerQualityChanged(videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
//Your code here
}
}
I'm trying to use delegation and property observers together to know when a property changes. I setup the protocol but I'm not sure how to use property observers.
I have a class called GridView that is being added to DetailViewController. GridView has an array of ints called rowValues. I would like to observe rowValues from DetailViewController.
GridView.swift
protocol gridViewDelegate {
func rowValueChanged(value: [Int])
}
class GridView: UIView {
var rowValues = [0,0,0,0,0]
var delegate: gridViewDelegate?
func updateRowValue() {
rowValues[0] = 1
}
}
DetailViewController.swift
class DetailViewController: UIViewController, gridViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
var grid = GridView(frame: view.frame)
grid.delegate = self
view.addSubview(grid)
}
func rowValueChanged(value: [Int]) {
println(value)
}
}
Probably this is the syntax you are looking for:
class GridView: UIView {
var rowValues: [Int] = [0,0,0,0,0] {
didSet {
if let theDelegate = self.delegate {
theDelegate.rowValueChanged(rowValues)
}
}
}
var delegate: gridViewDelegate?
}