Add a CollectionView in ScrollView - swift

I have a problem in implementing a UI. I actually want to add a collection view at the end of a scroll view. I don't exactly know how to proceed and I think I'm stuck...
Here is my call to add the views in the scroll view.
if training?.description != nil {
//add description
addTraining(description: (training?.description)!)
}
if training?.question1 != nil{
addTraining(question1: (training?.question1)!)
}
if training?.question2 != nil{
addTraining(question2: (training?.question2)!)
}
// ADD COLLECTION VIEW AT BOTTOM
Here would be the respective functions.
private func addTraining(description: String) {
trainingDescriptionTitle.text = "Description"
trainingDescriptionContent.text = description
trainingDescriptionContent.sizeToFit()
trainingDescriptionContent.frame.size = CGSize(width: view.frame.width - 10, height: trainingDescriptionContent.frame.height)
trainingDescriptionContainerView.addView(newView: trainingDescriptionTitle)
trainingDescriptionContainerView.addView(newView: trainingDescriptionContent)
scrollView.addView(newView: trainingDescriptionContainerView)
}
private func addTraining(question1: String) {
trainingQuestion1Title.text = "What You Will Learn"
trainingQuestion1Content.text = question1
trainingQuestion1Content.sizeToFit()
trainingQuestion1Content.frame.size = CGSize(width: view.frame.width - 10, height: trainingQuestion1Content.frame.height)
trainingQuestion1ContainerView.addView(newView: trainingQuestion1Title)
trainingQuestion1ContainerView.addView(newView: trainingQuestion1Content)
scrollView.addView(newView: trainingQuestion1ContainerView)
}
private func addTraining(question2: String) {
trainingQuestion2Title.text = "Who Should Take This"
trainingQuestion2Content.text = question2
trainingQuestion2Content.sizeToFit()
trainingQuestion2Content.frame.size = CGSize(width: view.frame.width - 10, height: trainingQuestion2Content.frame.height)
trainingQuestion2ContainerView.addView(newView: trainingQuestion2Title)
trainingQuestion2ContainerView.addView(newView: trainingQuestion2Content)
scrollView.addView(newView: trainingQuestion2ContainerView)
}
how would I do the same in adding a collectionView in the scrollView?
Thanks to anyone who'll answer.
Here would be the result:

Related

Change Google's map SDK current location button possition [duplicate]

I am using google maps in my project i want to show my location button
a bit up how should i do that using swift
You can change the position of the MapControls by set the padding for your map view .
let mapView = GMSMapView()
mapView.isMyLocationEnabled = true
mapView.settings.myLocationButton = true
mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 50)
It will change the padding of the my location control. From bottom 50px and from right 50px
Refer the below screenshot which added the padding of 50px from bottom and right.
Update for swift 4 : just change the position of my location button only
for object in self.mapView.subviews{
if(object.theClassName == "GMSUISettingsPaddingView"){
for view in object.subviews{
if(view.theClassName == "GMSUISettingsView"){
for btn in view.subviews{
if(btn.theClassName == "GMSx_QTMButton"){
var frame = btn.frame
frame.origin.y = frame.origin.y - 100
btn.frame = frame
}
}
}
}
}
}
public extension NSObject {
public var theClassName: String {
return NSStringFromClass(type(of: self))
}
}
Like Subramanian, this works very well :
mapView.padding = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
This is perfect if you have a navigation bar in your app.
Here's my function for Google Maps SDK 5.1.0, since the button's class was renamed to 'GMSx_MDCFloatingButton' in case someone needs an updated approach.
Also depending on your implementation you might want to call this on viewDidAppear().
func moveLocationButton(){
for object in self.googleMapsView!.subviews{
if(object.thisClassName == "GMSUISettingsPaddingView"){
for view in object.subviews{
if(view.thisClassName == "GMSUISettingsView"){
for btn in view.subviews{
if(btn.theClassName == "GMSx_MDCFloatingButton"){
var frame = btn.frame
frame.origin.x = frame.origin.x - 50
frame.origin.y = frame.origin.y - 150
btn.frame = frame
}
}
}
}
}
}
}
public extension NSObject {
var thisClassName: String {
return NSStringFromClass(type(of: self))
}
}
I would recommend to change button position by correcting constraint instead of changing frame.origin. I got it worked by changing frame.origin placing it into viewDidAppear only. Neither viewWillApper nor viewWillLayoutSubviews were correct methods to call from. However I managed it work perfectly calling constraint changing from viewDidLoad. And I used snapkit, but it is not a big deal to get it done natively
private func changeCurrentLocationButtonPosition(of mapView: GMSMapView) {
for object in mapView.subviews where object.theClassName == "GMSUISettingsPaddingView" {
for view in object.subviews where view.theClassName == "GMSUISettingsView" {
for btn in view.subviews where btn.theClassName == "GMSx_MDCFloatingButton" {
btn.snp.makeConstraints({ maker in
maker.left.equalToSuperview().offset(15)
})
btn.snp.makeConstraints({ maker in
maker.bottom.equalToSuperview().offset(-30)
})
return
}
}
}
}
Use this function and call this at viewDidLayoutSubviews()
func changeLocationBtnFrame(){
self.mapView.settings.myLocationButton = true
for obj in self.mapView.subviews{
if String(describing: obj.classForCoder) == "GMSUISettingsPaddingView"{
for viw in obj.subviews{
if String(describing: viw.classForCoder) == "GMSUISettingsView"{
for btn in viw.subviews{
if String(describing: btn.classForCoder) == "GMSx_MDCFloatingButton"{
var frame = btn.frame
btn.frame = CGRect(x: 316, y: 0, width: 56, height: 56)
//USE you want frame this line
break
}
}
break
}
}
break
}
}
}

HomeKit Camera Streaming Not Working in Swift

I've tried building a simple HomeKit app to stream video from a HomeKit Camera and while it appears to be working - the stream is not showing on the View Controller. Any help appreciated.
#IBOutlet weak var liveStreamView: HMCameraView!
// var liveStreamView: HMCameraView?
func startCameraStream(for accessory: HMAccessory) {
// Ensure this is a camera accessory
guard let cameraStreamControl = accessory.cameraProfiles?.first?.streamControl else
{ return }
cameraStreamControl.delegate = self
cameraStreamControl.startStream()
let liveStreamView = HMCameraView()
self.view.addSubview(liveStreamView)
self.liveStreamView = liveStreamView
self.liveStreamView.cameraSource = cameraStreamControl.cameraStream
self.liveStreamView?.setNeedsDisplay()
self.view.layoutIfNeeded()
}
extension ViewController: HMCameraStreamControlDelegate {
func cameraStreamControlDidStartStream(_ cameraStreamControl: HMCameraStreamControl) {
liveStreamView?.cameraSource = cameraStreamControl.cameraStream
}
}
So silly. I forgot to define the liveStreamView attributes including the frame. See below where I set the background color, alpha, tag, etc:
func startCameraStream( for accessory: HMAccessory) {
// Ensure this is a camera accessory
guard let cameraStreamControl = accessory.cameraProfiles?.first?.streamControl else
{ return }
cameraStreamControl.delegate = self
cameraStreamControl.startStream ( )
let liveStreamView = HMCameraView(frame: CGRect(x: 0, y: 0, width: 320, height: 568))
liveStreamView.backgroundColor = .clear
liveStreamView.alpha = 0.5
liveStreamView.tag = 100
liveStreamView.isUserInteractionEnabled = true
self.view.addSubview(liveStreamView)
self.liveStreamView = liveStreamView
}

Identifying Objects in Firebase PreBuilt UI in Swift

FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.

SwiftyGif Remote Gif is running but will not display

For the life of me I can't get the GIF to display using the SwiftyGif library. Is there something I'm missing here?
var outgoingMessageView: UIImageView!
outgoingMessageView = UIImageView(frame:
CGRect(x: llamaView.frame.maxX - 50,
y: llamaView.frame.minY + 75,
width: bubbleImageSize.width,
height: bubbleImageSize.height))
outgoingMessageView.delegate = self
if textIsValidURL == true {
print("URL is valid")
outgoingMessageView.image = bubbleImage
let maskView = UIImageView(image: bubbleImage)
maskView.frame = outgoingMessageView.bounds
outgoingMessageView.mask = maskView
outgoingMessageView.frame.origin.y = llamaView.frame.minY - 25
let url = URL(string: text)
outgoingMessageView.setGifFromURL(url, manager: .defaultManager, loopCount: -1, showLoader: true)
} else {
outgoingMessageView.image = bubbleImage
}
// Set the animations
label.animation = "zoomIn"
//outgoingMessageView.animation = "zoomIn"
// Add the Subviews
view.addSubview(outgoingMessageView)
print("outgoingMessageView added")
The delegate lets me know it runs successfully via:
gifDidStart
gifURLDidFinish
Checking outgoingMessageView.isAnimatingGif() tells me it's still running.
Checking outgoingMessageView.isDisplayedInScreen(outgoingMessageView) tells me it's not being displayed
It "finishes" almost immediately, but it's the same in the example project, yet the gif still loops and displays in the project. I've changed loop counts, imageviews, not running via a mask as I intended and instead just a UIImageView, changed the GIF urls, all to no avail. Is this problem related to my view structure?
I am calling this function based on actions in a collectionView.Image Example Here
Using the latest SwiftyGIF version.
I just made a sample about this with the following code:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
testSwiftyGif()
}
public func testSwiftyGif() {
let imgPath = "https://github.com/kirualex/SwiftyGif/blob/master/SwiftyGifExample/1.gif?raw=true"
let imgUrl = URL(string: imgPath)!
var outgoingMessageView: UIImageView!
outgoingMessageView = UIImageView(frame:
CGRect(x: llamaView.frame.maxX - 50,
y: llamaView.frame.minY + 75,
width: 200,
height: 200))
outgoingMessageView.setGifFromURL(imgUrl, manager: .defaultManager, loopCount: -1, showLoader: true)
self.view.addSubview(outgoingMessageView)
print("outgoingMessageView added")
}
And it adds the gif as intended:
Aparently the issue is your view structure. The image is being added to the view, but the view is not visible due mask, frame or superview position.
Try to check the view hierarchy using the xCode View Hierarchy Debugger

UISlider not updating values

apologies if this is a stupid question. I can't seem to get my slider to update its value as its being interacted with. (I'm going to point everyone to the very last method in this long code)
class CustomSlider: UISlider {
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var rect = super.trackRect(forBounds: bounds)
rect.size.height = 7
return rect
}
}
class FactionButton: CustomSlider {
var factionSlider = CustomSlider(frame: CGRect(x: 15, y: 542, width: 386, height: 57))
func factionBalanceSlider(){
factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider")
factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider")
factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal)
factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8)
factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59)
factionSlider.setValue(0.5, animated: true)
factionSlider.isContinuous = false
factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)), for: .valueChanged)
}
func getSlider() -> CustomSlider {
return factionSlider
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
var customBounds = super.trackRect(forBounds: bounds)
customBounds.size.height = 10
return customBounds
}
#objc func recordFactionBalance(sender: CustomSlider){
//also calculates balance and adds it into the quiz data
print("hi")
print(sender.value) //It's this part that doesn't work
}
}
It's this bit nearest to the bottom that has the issue. (Everything else is fine) The action function doesn't seem to be triggered at all, even when I'm interacting with it. Neither print statements are being executed. Any ideas why?
Cheers
From the getSlider(), i can guess you are using this class as a utility to get the CustomSlider. So, i suspect you are adding the slider to the view as below,
let container = FactionButton()
container.factionBalanceSlider()
let slider = container.getSlider()
self.view.addSubview(slider)
If you will not add the container to the view which is set as the receiver for .valueChange event so it will not get any event. To receive events you also need to add the container in the view as below,
self.view.addSubview(container)