How to construct a ScrollView using Swift? - ios

I'm constructing my first App for IOS and I'm struggling to find a way to do a simple ScrollView using the Swift code on the XCode6, please can someone help me to find the solution?
My problem is that I don't know how to make the scrollview work in my code. I already putted the code as you can see below in the ViewController.swift and I was expecting to be able to select the Outlet "scroller" in the Main.storyboard for a ViewController, instead of this I'm receiving the error *"fatal error: Can't unwrap Optional.None (lldb)"* EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)
I have some ViewController screens and in one of that I putted one ScrollView and I want to make it works using the Swift.
I'm stuck on this:
import UIKit
class ViewController: UIViewController {
#IBOutlet var scroller:UIScrollView
override func viewDidLoad() {
super.viewDidLoad()
scroller.scrollEnabled = true;
scroller.contentSize = CGSizeMake(320, 624);
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I think if someone can provide a simple example how to do a scrollview using swift it will solve my problem. Any help is appreciate.
Trying to do it in a old style I tried to do it using a .m and .h file:
ViewController.m
#import "Amigo-Bridging-Header.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[scroller setScrollEnabled:YES];
[scroller setContentSize:CGSizeMake(320, 624)];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Amigo-Bridging-Header.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
IBOutlet UIScrollView *scroller;
}
#end
Cheers

Let's give this a shot. The one thing to note is I have yet to find a way to downcast self.view as a UIScrollView, so you can't make calls like self.view.contentOffset.
import UIKit
class ScrollingViewController : UIViewController {
// Create a scrollView property that we'll set as our view in -loadView
let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)
override func loadView() {
// calling self.view later on will return a UIView!, but we can simply call
// self.scrollView to adjust properties of the scroll view:
self.view = self.scrollView
// setup the scroll view
self.scrollView.contentSize = CGSize(width:1234, height: 5678)
// etc...
}
func example() {
let sampleSubView = UIView()
self.view.addSubview(sampleSubView) // adds to the scroll view
// cannot do this:
// self.view.contentOffset = CGPoint(x: 10, y: 20)
// so instead we do this:
self.scrollView.contentOffset = CGPoint(x: 10, y: 20)
}
}

Your outlet is not connected. From the Swift with objective-C book:
When you declare an outlet in Swift, the compiler automatically converts the type to a weak implicitly unwrapped optional and assigns it an initial value of nil. In effect, the compiler replaces #IBOutlet var name: Type with #IBOutlet weak var name: Type! = nil
If this value was not connected, it would remain as nil and you'd get a runtime error when accessing the value.

#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// setup the scroll view
self.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 200, 0);
}

Related

Get a control's original frame for both portrait and landscape

I am working on an application that has different UI constraints and control positions for portrait and landscape. These are all done on the storyboard. In addition to this, I am repositioning controls based on a user shutting off one of the controls. I am doing this by grabbing the frame for each of the controls in viewDidLoad. Once I have these values, then it is easy to reposition the controls and restore them to what they should be when unhidden. The thing is that I need all of the frames for both portrait and landscape. That way I can do the repositioning regardless of orientation.
How can I get the control positioning information for both portrait and landscape when coming through viewDidLoad? Is there any way to do this?
After adding constraints to a view, and the view readjusting its position and size according to device size and orientation. This readjusting of the view size is done in the method viewDidLayoutSubviews, which is called after viewDidAppear.
If you can log out the positions and size of the control in this method, you will get the updated (size and position as it is seen in the device).
But this method is called multiple times after viewDidAppear, so if you want to add anything i recommend adding the control in viewDidLoad and then updating the position in this method.
After working with this a bit more, I came up with this:
import UIKit
class ViewController: UIViewController {
var pButtonFrame: CGRect!
var lButtonFrame: CGRect!
#IBOutlet weak var testButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
NSNotificationCenter.defaultCenter().addObserver(self, selector: "screenRotated", name: UIDeviceOrientationDidChangeNotification, object: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func screenRotated() {
//Set this only once, the first time the orientation is used.
if lButtonFrame == nil && UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation)
{
lButtonFrame = testButton.frame
}
else if pButtonFrame == nil && UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation)
{
pButtonFrame = testButton.frame
}
}
}
I set up a test button and positioned it using constraints on the storyboard. I added an observer to NSNotificationCenter to watch for screen rotation. I'm storing the frames for each of the orientations in CGRect variables. By checking each of the variables for nil, I can ensure that they only get set once, before I have done any modifications to the screen. That way, I can restore the values to their originals, if needed. I can set up the showing and hiding of controls here, or in viewDidLayoutSubviews.
import UIKit
class ViewController: UIViewController {
var pButtonFrame: CGRect!
var lButtonFrame: CGRect!
#IBOutlet weak var btntest: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
screenRotate()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func screenRotate() {
//Set this only once, the first time the orientation is used.
if lButtonFrame == nil && UIDeviceOrientationIsLandscape(UIDevice.currentDevice().orientation)
{
lButtonFrame = btntest.frame
}
else if pButtonFrame == nil && UIDeviceOrientationIsPortrait(UIDevice.currentDevice().orientation)
{
pButtonFrame = btntest.frame
}
}
}

How to make UI elements in class out of View Controller

I'm making app and I'm trying to make a View which contains a Label with a question. I want this view in my app and because I will use it repeatedly, I made a class (If I want to make some change, I can do It from one place). The UIView is called questionView (var questionView = UIView()). Problem is when I want to make questionView a subview of view. The error says that I don't have have "view" which I understand. I don't have view but how can I get it? Thank you
This is what is inside my Question class:
import Foundation
import UIKit
class Question {
// PROPERTIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
// ... next code, also not important
}
UPDATE:
There is my solution. It works BUT I think that it's not correct from a programming standpoint. Can anybody tell me anything about it? Thank you
My class in separate swift file:
My class in separate swift file:
class LabelClass {
var view = UIView()
init (view: UIView) {
self.view = view
}
var lbl = UILabel()
var lblView = UIView()
func makeLabel () {
self.lbl.frame = CGRectMake(0, 0, 150, 50)
self.lbl.text = "Text text text"
self.lbl.numberOfLines = 0
self.lblView.frame = CGRectMake(20, 20, 150, 50)
self.lblView.addSubview(self.lbl)
self.view.addSubview(lblView)
}
}
Piece of code my ViewController.swift:
override func viewDidLoad() {
super.viewDidLoad()
// Added code:
var object = LabelClass(view: self.view)
object.makeLabel()
}
I don't know Swift, but as far as I know, only instances of UIViewController have a view property, the class Question does not, so you cannot add subviews to it.
What you probably want is making a subclass of UIView which contains a question label, or to add the questionLabel as a subview of questionView.
It is because you are trying to add your view to a normal Swift class which doesn't have a self.view instance. Your Question class must be a subclass of UIViewController cocoa class that it has a self.view instance and override methods.
class Question:UIViewController {
// PROPHERITIES:
var questionLabel = UILabel()
var questionView = UIView()
// METHODS:
override func viewDidLoad() {
createQuestion("foo")
}
func createQuestion (input:String) {
// some code .... not important
// THIS:
self.view.addSubview(questionView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// ... next code, also not important
}

UITextView does not update its height after trying to change its height programmatically

I just want to modify the height of a UITextView.
The log resulting from the code below, says that in fact, the
height changed from 30 to 400:
println(txtresponses.frame.height) // returns 30
var newFrame:CGRect=txtresponses.frame
newFrame.size.height=400
txtresponses.frame=newFrame
println(txtresponses.frame.height) // returns 400
However, visually, the UITextView "txtresponses" remains with the same size.
I am new to Swift and Xcode, so all my tricks are already exhausted here, and I dont know if it is an iOS version issue, or some typical Xcode whim.
What is the correct way to modify a UITextView ´s height?
Make sure to call txtresponses.frame=newFrame in the main thread.
dispatch_async(dispatch_get_main_queue()) {
txtresponses.frame=newFrame
}
All UI updates must be done from the main thread.
Its not work because I think you are using Autolayout with constraint. Please check below url which may help you - Change height constraint programmatically
Might be issue Autolayout. u just remove the autolayout and check it will work. Check below code i hope it will help you.
Example :
import UIKit
class ViewController: UIViewController {
#IBOutlet var textView: UITextView!
#IBOutlet var butt: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textView.backgroundColor = UIColor.lightGrayColor()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func buttonAction(sender: UIButton) {
var newFrame:CGRect=textView.frame
newFrame.size.height=400
textView.frame=newFrame
}
}
Screen 1:
Screen 2:

Adding View Programmatically from ViewController super class doesn't show in front

I have a BaseViewController that my UIViewControllers extend so i can have explicit functions that i dont need to rewrite. Something i would like would be a functions such as self.showSpinner() and the viewController would show the spinner
My Code looks like this
class BaseViewController: UIViewController {
var actvIndicator : UIActivityIndicatorView!
override func viewDidLoad() {
super.viewDidLoad()
self.actvIndicator = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
self.actvIndicator.color = UIColor.blackColor()
self.actvIndicator.backgroundColor = UIColor.blackColor()
self.actvIndicator.frame = CGRectMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2, 100, 100);
self.actvIndicator.center = self.view.center
self.actvIndicator .startAnimating()
self.view.addSubview(self.actvIndicator)
self.actvIndicator.bringSubviewToFront(self.view)
self.edgesForExtendedLayout = UIRectEdge.None
self.navigationController?.navigationBar.translucent = false
}
func showSpinner(){
self.actvIndicator.startAnimating()
}
func hideSpinner(){
self.actvIndicator.stopAnimating()
}
}
And my viewcontrollers looks like this
class MyProjectViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.showSpinner()
}
}
MyProjectViewController have UITableView that fills the entire screen. When i set tblProjects.alpha = 0 i can see the spinner. But i want it in the front.
i also tried self.view.bringSubviewToFront(self.actvIndicator)
What am i missing?
A couple quick notes before I get into what I think your problem is:
When you add a subview it is automatically added to the top layer, no need for the bringSubviewToFront: in viewDidLoad: (which is being used wrong anyway).
You should not set view frames in viewDidLoad: (e.g. centering a view). Frames are not setup yet, so you should move that to viewWillAppear: or some other variant.
Now your issue is most likely a view hierarchy problem (further confirmed by your comment) and thus can probably be fixed by pushing the spinner to the front every time you want it to be shown, like:
func showSpinner() {
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}
The problem here stands on the fact that table view is draw after you are calling self.view.bringSubviewToFront(self.actvIndicator). A possible workaround for this is to call bringSubviewToFront when showing the spinner
func showSpinner(){
self.view.bringSubviewToFront(self.actvIndicator)
self.actvIndicator.startAnimating()
}

UIViewController with inputAccessoryView is not deallocated

I have simple subclass of UIViewController (code below).
If I attach inputAccessoryView, my viewcontroller is never deallocated. If I do not set inputAccessoryView in viewDidLoad, dealloc is called as expected.
Am I missing something?
#interface IMTestViewController : UIViewController
#property (nonatomic, strong) UIView *messageInputView;
#property(nonatomic, readwrite, strong) UIView *inputAccessoryView;
#end
#implementation IMTestViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.inputAccessoryView = self.messageInputView;
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (UIView *)messageInputView
{
if (_messageInputView == nil)
{
_messageInputView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 45)];
_messageInputView.autoresizingMask = UIViewAutoresizingFlexibleWidth;
}
return _messageInputView;
}
#end
I have ran out of ideas.
Thank you.
Unfortunately for me #rdelmar's answer didn't work. After some time spent trying to solve it I found this article: http://derpturkey.com/uitextfield-docked-like-ios-messenger/
My goal is to have the input accessory view visible even if the keyboard is not, exactly like in all IM apps. I previously subclassed my UIViewController custom class to allow it to become first responder and returned my custom subview as inputAccessoryView. This was preventing the view controller from being dealloced. Now I subclass the controller's view to achieve the same thing as recommended in the link above, everything seems to work fine.
EDIT: after some more testing I can confirm the custom UIView is dealloced just fine.
EDIT 2: only downside is that you can't make the keyboard appear in viewWillAppear, the inputAccessoryView is not already added to the view hierarchy and can't become first responder.
This question is rather old, but I came across it in 2019 when trying to use an inputAccessoryView in iOS 12.
The deallocation problem still exists today and the first solution proposed in the article mentioned in dvkch's answer does not work either. The second solution in the article (involving animations) is just too much work and does not work well when the user dismisses the keyboard interactively via a UIScrollView with scrollView.keyboardDismissMode = .interactive.
The best approach I could come up with is just setting the first responder UITextField or UITextView inputAccessoryView to nil on viewDidDisappear. That gets rid of the memory leak entirely and does not seem to have any side-effects or downsides.
So here's a full Swift 4.2 example:
class MyViewController: UIViewController {
/// You could also add your text field or text view programmatically,
/// but let's say it's coming from a .xib for now...
#IBOutlet private weak var myTextField: UITextField!
/// This is required for the inputAccessoryView to work.
override internal var canBecomeFirstResponder: Bool {
return true
}
/// Here's a nice empty red view that will be used as an
/// input accessory.
private lazy var accessoryView: UIView = {
let accessoryView = UIView()
accessoryView.backgroundColor = UIColor.red
accessoryView.frame.size = CGSize(
width: view.frame.size.width,
height: 45
)
return accessoryView
} ()
override var inputAccessoryView: UIView? {
return accessoryView
}
/// This is required to avoid leaking the `inputAccessoryView`
/// when the keyboard is open and the `UIViewController`
/// is deallocated.
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
myTextField.inputAccessoryView = nil
}
}

Resources