getting an IBOutlet from another class swift - ios

I have a IBOutlet in a class Main_Screen which is avaliable in a class hooked up to a main ViewController which has a ScrollView but if i try to get it returns nil
code in View Controller
import UIKit
class ViewController: UIViewController , UIScrollViewDelegate {
#IBOutlet weak var scrollVieww: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.scrollVieww.pagingEnabled = true
self.scrollVieww.delegate = self
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let vc = storyboard.instantiateViewControllerWithIdentifier("MainScreen") as? Main_Screen {
// imageview returns nil :(
let imageView = vc.avatarImageView
}
// Do any additional setup after loading the view, typically from a nib.
let V1 = self.storyboard?.instantiateViewControllerWithIdentifier("HomeScreen") as UIViewController!
//Add initialized view to main view and its scroll view and also set bounds
self.addChildViewController(V1)
self.scrollVieww.addSubview(V1.view)
V1.didMoveToParentViewController(self)
V1.view.frame = scrollVieww.bounds
//Initialize using Unique ID for the View
let V2 = self.storyboard?.instantiateViewControllerWithIdentifier("MainScreen") as UIViewController!
//Add initialized view to main view and its scroll view also set bounds
self.addChildViewController(V2)
self.scrollVieww.addSubview(V2.view)
V2.didMoveToParentViewController(self)
V2.view.frame = scrollVieww.bounds
//Create frame for the view and define its urigin point with respect to View 1
var V2Frame: CGRect = V2.view.frame
V2Frame.origin.x = self.view.frame.width
V2.view.frame = V2Frame
//The width is set here as we are dealing with Horizontal Scroll
//The Width is x3 as there are 3 sub views in all
self.scrollVieww.contentSize = CGSizeMake((self.view.frame.width) * 2, (self.view.frame.height))
}

Short answer: Don't do that. You should treat another view controller's views as private.
If you need to manipulate another view controller's UI, add public methods that you use to request the UI change, and then have code inside the VC make the change.
This is both much better design, and it avoids cases where the other view controller's views haven't been created yet, so they're nil and it fails/crashes with an "encountered nil when trying to unwrap optional" message.

Related

UIScrollView with glitch content on iPhone 11 Pro Max

I’m new in Swift and I tried making UIScrollView that shows view controllers.
Every thing perfect just at iPhone 11 Pro Max the next screen show a little bit on the side:
the orange strip is the next screen
My Code:
//MARK: - outlets
#IBOutlet weak var pageControl: UIPageControl!
#IBOutlet weak var scrollView: UIScrollView!
//MARK: - properties
var viewControllers: [String] = ["ComputerViewController", "AttactViewController", "DefenceViewController", "OfflineViewController"]
var frame = CGRect(x: 0, y: 0, width: 0, height: 0)
//MARK: - life cyrcles
override func viewDidLoad() {
super.viewDidLoad()
for index in 0..<viewControllers.count {
frame.origin.x = scrollView.frame.size.width * CGFloat(index)
frame.size = scrollView.frame.size
let view = UIView(frame: frame)
let storyboard = UIStoryboard(name: "Menu", bundle: nil)
var controller: UIViewController = storyboard.instantiateViewController(withIdentifier: viewControllers[index]) as UIViewController
view.addSubview(controller.view)
self.scrollView.addSubview(view)
}
scrollView.contentSize = CGSize(width: scrollView.frame.size.width * CGFloat(viewControllers.count), height: scrollView.frame.size.height)
scrollView.delegate = self
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var pageNumber = scrollView.contentOffset.x / scrollView.frame.size.width
pageControl.currentPage = Int(pageNumber)
}
thanks for helping...
A couple of observations:
You should avoid referencing frame in viewDidLoad. At this point, the frame is not known.
You should avoid hard-coding the size and placement of the subviews at all. There can be a variety of events that change the view’s size later on (e.g. rotations, split view multitasking, etc.). Use constraints.
With scroll views, there are two layout guides, one for its frame and another for its contentSize. So set the size of the subviews using the frameLayoutGuide and the placement of these subviews relative to the contentLayoutGuide.
When you add a view controller’s view as a subview, make sure you call addChild(_:) and didMove(toParent:) calls for view controller containment. See “Implementing a Container View Controller” section of the view controller documentation.
If you want to add paging behavior, just set isPagingEnabled of the scroll view.
Pulling that all together:
override func viewDidLoad() {
super.viewDidLoad()
addChildViews()
}
override func viewDidLoad() {
super.viewDidLoad()
var anchor = scrollView.contentLayoutGuide.leadingAnchor
for identifier in viewControllers {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let child = storyboard.instantiateViewController(withIdentifier: identifier)
addChild(child) // containment call
child.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(child.view)
child.didMove(toParent: self) // containment call
NSLayoutConstraint.activate([
// define size of child view (relative to `frameLayoutGuide`)
child.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
child.view.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor),
// define placement of child view (relative to `contentLayoutGuide`)
child.view.leadingAnchor.constraint(equalTo: anchor),
child.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
child.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
])
anchor = child.view.trailingAnchor
}
anchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor).isActive = true
scrollView.isPagingEnabled = true
}
I’ve eliminated the property frame as that’s not needed anymore (and is just a source of confusion with the view controller’s view’s property of the same name). I’ve also eliminated the container view as it didn’t add much to the overall solution (and only adds a layer of constraints to add).
But the key is to use constraints to dictate the size and position of the subviews within the scroll view and to use view controller containment API.

Views that are loaded in scroll view not sizing correctly

I have 3 views that I'm loading into a scroll view so users can swipe through the app to change views (similar to Snapchat or Tinder navigation). However, I added a horizontally and vertically centered label to one of the nibs and it appears as though the size is much wider than my screen. I have the nib frames set to the size of the device screen.
How can I make the subviews match the frame of the view (full screen)?
Here's what I've got in the viewDidLoad for the view controller with a full screen scroll view in it, as well of a screenshot of the result:
#IBOutlet weak var scrollViewOfViews: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let settingsVC = UIViewController(nibName: "SettingsViewController", bundle: nil)
let friendsFeedVC = UIViewController(nibName: "FriendsFeedViewController", bundle: nil)
let notificationsVC = UIViewController(nibName: "NotificationsViewController", bundle: nil)
let screenSize = UIScreen.mainScreen().bounds
settingsVC.view.frame = screenSize
friendsFeedVC.view.frame = screenSize
notificationsVC.view.frame = screenSize
self.addChildViewController(settingsVC)
self.scrollViewOfViews.addSubview(settingsVC.view)
settingsVC.didMoveToParentViewController(self)
self.addChildViewController(friendsFeedVC)
self.scrollViewOfViews.addSubview(friendsFeedVC.view)
friendsFeedVC.didMoveToParentViewController(self)
self.addChildViewController(notificationsVC)
self.scrollViewOfViews.addSubview(notificationsVC.view)
notificationsVC.didMoveToParentViewController(self)
var friendsFeedFrame = friendsFeedVC.view.frame
friendsFeedFrame.origin.x = self.view.frame.width
friendsFeedVC.view.frame = friendsFeedFrame
var notificationsFrame = notificationsVC.view.frame
notificationsFrame.origin.x = self.view.frame.width * 2
notificationsVC.view.frame = notificationsFrame
self.scrollViewOfViews.contentSize = CGSize(width: self.view.frame.width * 3, height: self.view.frame.size.height)
// start scrollview page on middle view
var startFrame = scrollViewOfViews.frame
startFrame.origin.x = self.view.frame.width
startFrame.origin.y = 0
scrollViewOfViews.scrollRectToVisible(startFrame, animated: false)
}
The result for the friendsFeedVC with a centered Label:
"You should not initialize UI geometry-related things in viewDidLoad, because the geometry of your view is not set at this point and the results will be unpredictable"
"viewWillAppear: is the correct place to do these things, at this point the geometry is set so getting and setting geometry-related properties makes sense."
Or you can set them in viewDidLayoutSubviews
Try to place the codes that set frame of views in viewWillAppear or viewDidLayoutSubviews
Ref: Why am I having to manually set my view's frame in viewDidLoad?

ScrollView on an iOS app in Swift

I have a ViewController and my View is too large, so I'm trying to implement a simple ScrollView but I don't know how to do. I Tried to do Embed in >> ScrollView but it doesn't work.
Add Scrollview in the xib or storyboard file of your VC. Create the view seperately and add it as a subview inside the scrollview. Or you could simply drag it inside the scrollview. Set content size of scrollview to be equal to that view's size. If you still need help, use this tutorial. http://www.raywenderlich.com/76436/use-uiscrollview-scroll-zoom-content-swift
If you need wide screen so here is example how you can make three times wider that usual one.
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let view1: View1 = View1(nibName: "View1", bundle: nil)
addChildViewController(view1)
scrollView.addSubview(view1.view)
view1.didMoveToParentViewController(self)
let view2: View2 = View2(nibName: "View2", bundle: nil)
addChildViewController(view2)
scrollView.addSubview(view2.view)
view2.didMoveToParentViewController(self)
var view2Frame: CGRect = view2.view.frame
view2Frame.origin.x = view.frame.width
view2.view.frame = view2Frame
let view3: View3 = View3(nibName: "View3", bundle: nil)
addChildViewController(view3)
scrollView.addSubview(view3.view)
view3.didMoveToParentViewController(self)
var view3Frame: CGRect = view3.view.frame
view3Frame.origin.x = view.frame.width * 2
view3.view.frame = view3Frame
self.scrollView.contentSize.width = view.frame.width * 3
}
So basically you create scrollView and add here three views and each view has it's new origin point. You can enable or disable paging in storyboard. And that is it!

Using View from Storyboard for ImagePicker

I'm trying to create an overlay on top of an ImagePicker, and finding I can create the UIView programmatically, but would prefer to use a UIView that I can manipulate within Storyboard.
As you can see from the code snipper below, I've assigned the UIImagePickerControllerDelegate to my ViewController, created the image picker, and have a method for assigning the overlay view that I want. If I try to reference a UIView that is an outlet tied to a UIView that exists in the storyboard, it doesn't appear. If If create it programmatically, all good. How can I assign it to the UIView that I want to drag-drop controls onto in Storyboard?
Thanks in advance for any advice/guidance.
Cheers,
Carl
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// UIView for telemetry data
#IBOutlet weak var telemetryPanel: UIView!
...
override func viewDidAppear(animated:bool) {
...
let cameraImagePicker = UIImagePickerController()
...
// Call method to create a custom overlay for the camera
cameraImagePicker.cameraOverlayView = self.customViewForImagePicker(cameraImagePicker)
}
func customViewForImagePicker(imagePicker: UIImagePickerController!) -> UIView {
// TODO: - Assigning the view to the telemetry panel does not work
// let view:UIView = self.telemetryPanel
// view.backgroundColor = UIColor.whiteColor()
// view.alpha = 0.25
// return view
// Creating the view programmatically overlays it
let cameraAspectRatio:CGFloat = 4.0 / 3.0;
let screenSize = UIScreen.mainScreen().bounds.size
let imageWidth = floorf(Float(screenSize.width) * Float(cameraAspectRatio))
let view: UIView = UIView(frame: CGRectMake(0, screenSize.height-65, CGFloat(imageWidth), 65))
view.backgroundColor = UIColor.whiteColor()
view.alpha = 0.25
return view
}
When initialising a view that has been created in interface builder, you will need to load it using NSBundle.mainBundle().loadNibNamed()
Check out this answer on how to do that.

Using UIScrollView to cycle through images

I have an imageView inside of a scrollView, and I want to be able to upload up to 3 images and have the user swipe between them. I have accomplished this simply by replacing the contents of the view with the information I want, but it doesn't look or feel as good as a scroll view with pagination would.
I have all of the code to make each image appear when I want it, but my problem is with the scrollview constraints and how to add more content to a scrollview with fixed width. No matter what I do, I can't seem to extend the scroll range when I add an image to the view.
here is what I'm trying now:
let newImageView:UIImageView = UIImageView()
self.imageScrollView.contentSize = CGSize(width: self.imageView.frame.size.width * 3, height: self.imageView.frame.size.height)
self.imageScrollView.pagingEnabled = true
for i = 0; i < 3; ++i {
var xOrigin:CGFloat = CGFloat(i) * self.view.frame.size.width
newImageView.frame = CGRectMake(self.imageView.image!.size.width * i, 0, self.imageView.image!.size.width, self.imageScrollView.frame.size.height)
newImageView.image = images[i]
newImageView.contentMode = UIViewContentMode.ScaleAspectFit
self.imageScrollView.addSubview(newImageView)
}
With this code I am able to add the images, but I cannot scroll to them. In my storyboard, the scrollview constraints look like this:
I'm sure some of these constraints are redundant, but I can't figure out why the content width won't extend and allow me to scroll through all of the images in the scrollview.
EDIT
I have made changes by using the textbook examples from #matt. I made a new view controller and put it inside a view in my other view controller (the same one as I had originally). It displays the first one no problem, but it doesn't seem to call teh UIPageViewControllerDataSource functions when I swipe left or right. I'm not sure why this is the case.
Here's what it looks like in the original view controller (UploadPhotoViewController)
func setUpPageViewController() {
sharedIm.images.append(UIImage(named: "placeholder")!)
sharedIm.images.append(UIImage(named: "TestProfile")!)
sharedIm.images.append(UIImage(named: "button")!)
let pvc = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: nil)
let page:ImagePage = ImagePage()
pvc.dataSource = self
pvc.setViewControllers([page], direction: .Forward, animated: false, completion: nil)
pvc.view.frame = self.imageContainer.bounds
self.imageContainer.addSubview(pvc.view)
pvc.didMoveToParentViewController(self)
}
//this is placed at the end of the class
extension UploadPhotoViewController : UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let image = (viewController as! ImagePage).image
let ix = find(sharedIm.images, image)! + 1
println(ix)
if ix >= sharedIm.images.count {
println("no next page")
return nil
}
sharedIm.currentIndex = ix
let page = ImagePage()
return page
}
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let image = (viewController as! ImagePage).image
let ix = find(sharedIm.images, image)! - 1
println(ix)
if ix < 0 {
println("no previous page")
return nil
}
sharedIm.currentIndex = ix
let page = ImagePage()
return page
}
And here is what is in my new view controller that is being added to a view of the other one:
class ImagePage: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var image : UIImage?
var imagePicker: UIImagePickerController!
#IBOutlet var imageView : UIImageView!
#IBOutlet var forwardButton: UIButton!
#IBOutlet var backButton: UIButton!
#IBOutlet var closeButton: UIButton!
#IBOutlet var pictureButton: UIButton!
#IBOutlet var cameraButton: UIButton!
init() {
self.image = sharedIm.images[sharedIm.currentIndex]
super.init(nibName: "ImagePage", bundle: nil)
}
required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}
override func viewDidLoad() {
super.viewDidLoad()
self.imageView.image = self.image
}
//bunch of other code for buttons and uploading pictures
}
What you're trying to do would be a lot easier with a UIPageViewController (with a scrolling style). I would suggest that you switch to a UIPageViewController implementation instead.
The reason is that you only supply one "page" (in your case, an image) at a time. You can decide in real time whether there is another "page" in either direction, and how many "pages" you want to pretend there "really" are. The bookkeeping work happens in your model (the data behind the scenes), rather than your having to configure the entire view to handle all pages at once, as UIScrollView makes you do.
When working with autolayout and uiscrollview, the trick is to know when to load your content. Since you are working in a hybrid mode (frames and autolayout) you need to make sure the constraints have had time to load and then AFTER they have loaded, you can add your subviews and adjust the constraints. If you try to load your content on viewDidLoad, it will be too early and your constraints may not have been set yet. If you load your content on viewWillAppear, then it's too late and the constraints have already been locked in for presenting the view to the user.
So, I have found the best place to load content is on the viewDidLayoutSubviews At this point autolayout has applied the constraints to your scrollView and you are now safe to make changes. Add a call here to run your code that populates the scrollView and you should be fine. Also, you will need to set a flag to only make the call once as viewDidLayoutSubviews can get called several times during view creation. You should also call setNeedsUpdateConstraints on your scrollView to make sure its constraints are updated as you add your subviews.

Resources