What is the proper way to structure Swift UIViews and UIViewControllers? [closed] - ios

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 6 years ago.
Improve this question
Does the UIViewController simply manage what UIViews are displayed? Or is it also used to control the contents within them?
For example, right now I currently have code in my UIViewController that switches from one view to the next based on a button click which looks like this:
class TTViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
super.view.addBackground()
}
//other code
#IBAction func OpenUserSelectView(sender: AnyObject) {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "PlayerSelectView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
super.view.addSubview(view)
}
}
But I also have UIViews that do things like create buttons, have actions for those buttons, as well as other things which looks like this:
class PlayerSelectView :UIScrollView {
let viewWidth = CGFloat(300)
let viewHeight = CGFloat(500)
var addPlayerButton :TTBlueButton?
var cancel :TTBlueButton?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let screenSize: CGRect = UIScreen.mainScreen().bounds
self.frame = CGRectMake((screenSize.width/2) - (viewWidth/2), (screenSize.height/2) - (viewHeight/2), viewWidth, viewHeight)
styleView()
addAddPlayerButton()
self.contentSize = CGSizeMake(viewWidth, viewHeight-100)
self.userInteractionEnabled = true
}
func styleView() {
super.layer.cornerRadius = 8.0
super.layer.shadowColor = UIColor.blackColor().CGColor
super.layer.shadowOpacity = 0.2
super.layer.shadowRadius = 4.0
super.layer.shadowOffset = CGSizeMake(0.0, 5.0)
}
func addAddPlayerButton(){
addPlayerButton = TTBlueButton(frame: CGRectMake(10, 10, viewWidth-20, 40))
addPlayerButton!.setTitle("Add New Player", forState: UIControlState.Normal)
addPlayerButton!.layer.cornerRadius = 5.0
addPlayerButton!.userInteractionEnabled = true
addPlayerButton!.addTarget(self, action: #selector(PlayerSelectView.addPlayer(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(addPlayerButton!)
}
func addCancelButton(){
addPlayerButton = TTBlueButton(frame: CGRectMake(10, 10, viewWidth-20, 40))
addPlayerButton!.setTitle("Cancel", forState: UIControlState.Normal)
addPlayerButton!.layer.cornerRadius = 5.0
addPlayerButton!.userInteractionEnabled = true
addPlayerButton!.addTarget(self, action: #selector(PlayerSelectView.addPlayer(_:)), forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(addPlayerButton!)
}
#IBAction func addPlayer(sender: AnyObject) {
if addPlayerButton?.currentTitle != "Create Player" {
let addPlayerTextField = TextField(frame: CGRect(x: 10, y: 10, width: viewWidth - 20, height: 40))
let border = CALayer()
let width = CGFloat(2.0)
addPlayerButton!.setTitle("Create Player", forState: UIControlState.Normal)
border.borderColor = UIColor.darkGrayColor().CGColor
border.frame = CGRect(x: 0, y: 0, width: addPlayerTextField.frame.size.width, height: addPlayerTextField.frame.size.height)
border.borderWidth = width
border.cornerRadius = 5
addPlayerTextField.layer.addSublayer(border)
addPlayerTextField.layer.masksToBounds = true
self.addSubview(addPlayerTextField)
addPlayerButton?.frame.origin = CGPoint(x: 10, y: 60)
} else {
}
}
}
Something about the way I've done this feels wrong so I was wondering if my suspicions were correct or if this is an acceptable way of doing this.

You can do whatever you like, but in my opinion, code configuration of what views appear goes into a controller (e.g. a view controller - note the name).
You don't have to do everything in one view controller - you can nest view controllers - but what you are showing is controller code and should (in my opinion) be in a view controller.

As the name suggests, a View Controller manages a set of Views and is
an instance of the Class UIViewController. Their job is to manage
things like view resizing, layouts, reacting to interaction with
views, handling events such as motion or touch, and coordinating with
data models or even other view controllers.
You need to study view naigation using navigation controller.beacuse on the above code you are adding a view into your's uiviewcontroller's view,and also the code have some memory leak too.
make one or more child view controllers and do what ever you want on that view controller.after that when you click a button from parent viewcontroller push your child view controller using uinavigationcontroller

Related

Making NavigationBar subview clickable in Swift

I have a View Controller embedded in Navigation Controller. The view has 1 WKWebView, hence, I'm setting view = webView in loadView() override.
So, I'm adding a small little sub navigation bar underneath my navigation controller to allow a user to change their location.I can add the subview to the navigation controller, I'm just not able to make it clickable.
override func loadView() {
let config = WKWebViewConfiguration()
config.processPool = YourModelObject.sharedInstance.processPool
webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
self.webView.scrollView.delegate = self
view = webView
..
if let navigationBar = self.navigationController?.navigationBar {
let secondFrame = CGRect(x: 50, y: 44.1, width: navigationBar.frame.width, height: 30)
let secondLabel = UILabel(frame: secondFrame)
secondLabel.textColor = .black
secondLabel.text = "Getting your location..."
secondLabel.isUserInteractionEnabled = true
let guestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(setLocation(_:)))
secondLabel.addGestureRecognizer(guestureRecognizer)
secondLabel.textAlignment = .left
secondLabel.font = secondLabel.font.withSize(14)
secondLabel.tag = 1002
navigationBar.addSubview(secondLabel)
}
}
And then the setLocation function
#objc func setLocation(_ sender: Any) {
print("location label tapped")
}
But when I tap the label, I'm not getting anything printed in console. I don't know if the use of target: self is wrong for the tapGestureRecognizer or what's going on here.
I too am new to Swift, so my answer is far from guaranteed. I just know what it's like to be in your position,
Perhaps try creating a subclass of navigationBar for the sub navigation bar, i.e. mySubNavigationBar. Then in the subclass's code do all the initialization that you need to do. Including the print line so you'll know if you're getting there.
p.s. I would have put this as a comment, but I don't have enough points to add comments.

how to custom flip and transition to another view controller with button click ? (n swift)

basically my current setup is like this
one storyboard ViewController with 3 types of UI View(container, front view, back view) inside of it.
what i want to accomplish (and i don't know how to implement #2)
user enters the data on the form(front of the card- View Controller number 1)
clicks the save button (do animation flipping and redirect to a new view controller)
the new view controller loads up (back of the card - View Controller number 2)
this is the current code flip example:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var goButton: UIButton!
#IBOutlet weak var optionsSegment: UISegmentedControl!
let owlImageView = UIImageView(image: UIImage(named:"img-owl"))
let catImageView = UIImageView(image: UIImage(named:"img-cat"))
var isReverseNeeded = false
override func viewDidLoad() {
super.viewDidLoad()
title = "Transitions Test"
setupView()
}
fileprivate func setupView() {
let screen = UIScreen.main.bounds
goButton.layer.cornerRadius = 22
//container to hold the two UI views
let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 250, height: 250))
containerView.backgroundColor = UIColor(red: 6/255, green: 111/255, blue: 165/255, alpha: 1.0)
containerView.layer.borderColor = UIColor.white.cgColor
containerView.layer.borderWidth = 2
containerView.layer.cornerRadius = 20
containerView.center = CGPoint(x: screen.midX, y: screen.midY)
view.addSubview(containerView)
//front view
catImageView.frame.size = CGSize(width: 100, height: 100)
catImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
catImageView.layer.cornerRadius = 50
catImageView.clipsToBounds = true
//back view
owlImageView.frame.size = CGSize(width: 100, height: 100)
owlImageView.center = CGPoint(x: containerView.frame.width/2, y: containerView.frame.height/2)
owlImageView.layer.cornerRadius = 50
owlImageView.clipsToBounds = true
containerView.addSubview(owlImageView)
}
#IBAction func goButtonClickHandler(_ sender: Any) {
doTransition()
}
fileprivate func doTransition() {
let duration = 0.5
var option:UIViewAnimationOptions = .transitionCrossDissolve
switch optionsSegment.selectedSegmentIndex {
case 0: option = .transitionFlipFromLeft
case 1: option = .transitionFlipFromRight
case 2: option = .transitionCurlUp
case 3: option = .transitionCurlDown
case 4: option = .transitionCrossDissolve
case 5: option = .transitionFlipFromTop
case 6: option = .transitionFlipFromBottom
default:break
}
if isReverseNeeded {
UIView.transition(from: catImageView, to: owlImageView, duration: duration, options: option, completion: nil)
} else {
UIView.transition(from: owlImageView, to: catImageView, duration: duration, options: option, completion: nil)
}
isReverseNeeded = !isReverseNeeded
}
}
There are a few alternatives for transition between view controllers with a flipping animation:
You can define a segue in IB, configure that segue to do a horizontal flipping animation:
If you want to invoke that segue programmatically, give the segue a “Identifier” string in the attributes inspector and then you can perform it like so:
performSegue(withIdentifier: "SecondViewController", sender: self)
Alternatively, give the actual destination view controller’s scene a storyboard identifier, and the presenting view controller can just present the second view controller:
guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") else { return }
vc.modalTransitionStyle = .flipHorizontal
vc.modalPresentationStyle = .currentContext
show(vc, sender: self)
If this standard flipping animation isn’t quite what you want, you can customize it to your heart’s content. iOS gives us rich control over custom transitions between view controller by specifying transitioning delegate, supplying an animation controller, etc. It’s a little complicated, but it’s outlined in WWDC 2017 Advances in UIKit Animations and Transitions: Custom View Controller Transitions (about 23:06 into the video) and WWDC 2013 Custom Transitions Using View Controllers.

How to overlay a view above a keyboard in iOS [duplicate]

This question already has answers here:
How to display UIView over keyboard in iOS
(7 answers)
Closed 5 years ago.
I need to present a help screen that overlays an open keyboard - the help screen should dim the whole view underneath and keep just a small hole with full transparency to "highlight" that piece. The point is to provide some information about several view components while highlighting them. Without a keyboard, I could just put a view at top of the hierarchy, but in this case the UI uses a keyboard with a custom input accessory that needs to be visible.
I tried to insert a new UIWindow and put it above all the UIWindows:
class CustomTextField: UITextField {
override var canResignFirstResponder: Bool {
return false
}
}
class ViewController: UIViewController {
var textField: UITextField = CustomTextField()
override func viewDidAppear(_ animated: Bool) {
view.backgroundColor = .white
super.viewDidAppear(animated)
textField.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
view.addSubview(textField)
textField.backgroundColor = UIColor.gray
textField.becomeFirstResponder()
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 1) {
self.window.windowLevel = 100000002.0 // based on experiments with UIApplication.shared.windows this should be the top most window
let controller = UIViewController()
controller.view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
self.window.rootViewController = controller
self.window.makeKeyAndVisible()
}
}
let window = UIWindow(frame: UIScreen.main.bounds)
}
But there are two problems with this approach:
The keyboard gets hidden as soon as the window becomes key and visible.
Even when using windowLevel = 100000002.0 it seems that the keyboard is above the window (the keyboard gets animated, so while hiding, I can see that its above my window).
Any ideas how to deal with these two problems? Is it even possible?
OK, as pointed out by #Krunal, this is kind of a duplicate of this question. The trick there is to add the overlay view to the window in which keyboard is (which happens to be the UIApplication.shared.windows.last):
class ViewController: UIViewController {
var textField: UITextField = UITextField()
override func viewDidAppear(_ animated: Bool) {
view.backgroundColor = .white
super.viewDidAppear(animated)
textField.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
view.addSubview(textField)
textField.backgroundColor = UIColor.gray
textField.becomeFirstResponder()
DispatchQueue.main.asyncAfter(wallDeadline: .now() + 1) {
// this does the trick
let customView = UIView(frame: self.view.bounds)
customView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
customView.layer.zPosition = CGFloat(Float.greatestFiniteMagnitude)
UIApplication.shared.windows.last?.addSubview(customView)
}
}
}

Create UIContainerView programmatically

I've just started to code my app in Swift 2 and avoiding the use of XIBs and storyboards.
However, I am unable to replicate the following feature. It's exactly what I wanted.
I've tried creating a UIView to perform the following using .backgroundColor and it works, however, I am unable to link it to my UIViewControllers. Just wondering how is it done? How do I link my UIView to my UIViewController?
Codes:
let subFrame : CGRect = CGRectMake(0,screenHeight*1/2.75,screenWidth,screenHeight)
var loginView = SignUpViewController()
let signUpView: UIView = UIView(frame: subFrame)
signUpView.backgroundColor = UIColor.redColor()
//Controls what each segment does
switch segmentView.indexOfSelectedSegment {
case 0:
self.view.addSubview(signUpView)
case 1:
self.view.addSubview(loginView)
default:
break;
}
I'm not even sure if .view.addSubview(xxx) overwrites/replaces the original subview if it is not this way. Is this the right way to do it?
Do not just start coding an app if you are not familiar with simple things of the OOP (Object-Oriented-Programming) language like Swift. This is not the way how to learn a programming language. Sure you could learn while experimenting but it is better to understand the book first before starting with more complex stuff. Read a few more pages of the Swift book from Apple. Most classes for iOS development are still Objective-C wrapped classes (reference type because the top superClass is probably NSObject; keep this in mind).
Here is the code example you wanted:
class ViewController: UIViewController {
let firstView = UIView()
let secondView = UIView()
let segmentedControlView = UISegmentedControl(items: ["firstView", "secondView"])
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor() // we need this for the playground
/* setup your view here */
/* add your sigment logic somewhere */
self.view.addSubview(self.segmentedControlView)
self.view.addSubview(self.firstView)
self.view.addSubview(self.secondView)
self.segmentedControlView.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: 44)
self.segmentedControlView.selectedSegmentIndex = 0 // enable the first segment
self.segmentedControlView.addTarget(self, action: "segmentIndexChanged:", forControlEvents: UIControlEvents.ValueChanged)
/* add your own frame calculation here */
/* I prefer AutoLayout, but for the example static frames will be fine */
self.firstView.frame.origin = CGPoint(x: 0, y: self.segmentedControlView.frame.origin.y + self.segmentedControlView.frame.height)
self.firstView.frame.size = CGSize(width: self.view.frame.width, height: self.view.frame.height - self.segmentedControlView.frame.origin.y)
// to prevent same code, we just copy the same frame from the firstView
// both will sit in the same place
self.secondView.frame = self.firstView.frame
/* lets add some colors so we'll see our views */
self.firstView.backgroundColor = UIColor.blueColor()
self.secondView.backgroundColor = UIColor.redColor()
self.secondView.hidden = true // when intializer the secondView is not visible
}
func segmentIndexChanged(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
self.firstView.hidden = false
self.secondView.hidden = true
case 1:
self.firstView.hidden = true
self.secondView.hidden = false
default:
break;
}
}
}
If you do not understand a function, should should look up its definition in the developer docs. (Like: addSubview)

Can we get an array of all storyboard views

I'm developping application in Swift.
This application has many view and I would like to put a UIProgressView on all views
Can we get an array of all storyboard views ?
for exemple :
self.progressBar = UIProgressView(progressViewStyle: .Bar)
self.progressBar?.center = view.center
self.progressBar?.frame = CGRect(x: 0, y: 20, width: view.frame.width, height: CGFloat(1))
self.progressBar?.progress = 1/2
self.progressBar?.trackTintColor = UIColor.lightGrayColor();
self.progressBar?.tintColor = UIColor.redColor();
var arrayViewController : [UIViewController] = [...,...,...]
for controller in arrayViewController {
controller.view.addSubview(self.progressBar)
}
Thank you
Ysée
I assume that what you really want is to have the progress displayed on every view IF there is an operation in progress.
There are many ways to do that (using delegation, NSNotificationCenter, …) but the easiest I can think of would be to rely on viewWillAppear
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// Check if there's an operation in progress and add progressView if relevant
}
For the user, it will effectively look like you added the progress view to all views.
Why not create a base class that has a lazy stored property of type UIProgressView ? Optionally you can have two methods setProgressViewHidden(hidden : Bool) in order to easily show and hide the progress view and setProgress(progress : Float) to update the progress. Then all your view controllers can subclass this base class and conveniently interact with the progress view.
class ProgressViewController : UIViewController {
lazy var progressView : UIProgressView = {
[unowned self] in
var view = UIProgressView(frame: CGRectMake(0, 20, self.view.frame.size.width, 3))
view.progress = 0.5
view.trackTintColor = UIColor.lightGrayColor()
view.tintColor = UIColor.redColor()
self.view.addSubview(view)
return view
}()
}
To read more about lazy stored properties, check: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html

Resources