I'm working on a custom keyboard and want to add some UIImageViews to the interface. I can load a .Xib fine as shown below, but when I try to create a UIImage within the main view it causes it to crash. Do you know why this is?
import UIKit
class KeyboardViewController: UIInputViewController {
override func updateViewConstraints() {
super.updateViewConstraints()
}
override func viewDidLoad() {
super.viewDidLoad()
let myView = NSBundle.mainBundle().loadNibNamed("KeyboardView", owner: nil, options: nil)[0] as UIView
self.view.addSubview(myView)
let image = UIImage(named: "grid.png")
let imageSize = CGSize(width: 216, height: 216)
var gridImageView = UIImageView(frame: CGRect(origin: CGPoint(x: 0, y: 0), size: imageSize))
gridImageView.image = image
//tried this- Crashes
self.addSubview(gridImageView)
//also tried this- Crashes
self.view.insertSubview(view: gridImageView, aboveSubview: myView)
}
If I manually add a ImageView to the .XIB, it also crashes. I feel like UIInputViewController will not let you use a Image View, even though the documentation would indicate it's possible. I'm usually an idiot with these things so am probably misunderstanding the docs:
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/inputView
Related
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.
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
I implemented horizontal(Paging) slide show of views, created separate xib and integrated with the view controller. Slide show is working as expected but I want add action to the every views so that from there I can move directly to the respective main content pages. Find the below code for implementation of slide show.
func createSlides() -> [Slide] {
let slide1:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.labelTitle.text = "Page1"
let slide2:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide2.labelTitle.text = "Page2"
return [slide1, slide2]
Slide Function
func setupSlideScrollView(slides : [Slide]) {
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< slides.count {
slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(slides[i])
}
}
Xib file
class Slide: UIView {
#IBOutlet weak var labelTitle: UILabel!
var onClickCallback: (() -> Void)?
override init (frame : CGRect) {
super.init(frame : frame)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
If I think what you are asking is right then do this. (BTW I am a bit rusty at this so please don't crucify me if I'm wrong). Also I don't know where you are designing this so I am going to assume it is in a different place to main.viewcontroller. If it isn't just add a button and create a segue to the content page.
Make a public var in createSlides Func and set it to the slide you are on. Eg: Slide 1. When slide two is opened it should update the var two slide two. For the sake of this we will call the var test
Add a button into your design file
Add a #IBAction into the viewcontroller. This #IBAction should be linked with your button.
inside this #IBAction create a segue to the content using the variable we created before. self.performSegue(withIdentifier: test, sender: self)
In the main view controller add two segues from the view with the slidshow to the view controller with the content (Assuming you only have two slides). One segue should be called slide 1 (The same name as the let you created in createSlides func.) The second should be called slide 2 (for obvious reasons.)
Now when your button in slide 1 is pressed it should perform the segue slide 1 and show your main content
Hope I helped, Toby.
Also if this doesn't work, is not what you want to achieve or is badly worded please comment what it is and I will try to fix it.
call this function and pass pageIndex where you want to jump:
func jumpToView(_ index : Int) {
let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0)
scrollView.setContentOffset(offset, animated: true)
}
Hope this what you are looking.
First line in function let offset = CGPoint(x: view.frame.width * CGFloat(index), y: 0), for calculate position where you want to move in scrollview. E.g : for first xib position will be CGPoint(x:0,y:0) and for 3rd xib, position will be CGPoint(x:200,y:0), consider each xib width is 100
setContentOffset use to scroll at specific location/point.
For more check Link
I want to show an animated gif image in my cocoa application.
I dragged the gif into Assets.xcassets in XCode. I was hoping that NSImageView can show a gif out of the box, so I tried the following code.
let imageView = NSImageView(frame: NSRect(x: 0, y: 0, width: 512, height: 512))
imageView.canDrawSubviewsIntoLayer = true
imageView.imageScaling = .ScaleNone
imageView.animates = true
imageView.image = NSImage(named: "loading-animation")
window.contentView?.addSubview(imageView)
The image does not show up. The above code works with a png image. How do I get this to work?
For me it works only if there is nothing set in the Attribute Inspector and with the code like that (hope, it will be useful for someone):
class FirstViewController: NSViewController {
#IBOutlet weak var imgImage1: NSImageView!
override func viewDidLoad() {
super.viewDidLoad()
let imgImage1 = NSImageView(frame: NSRect(x: 407, y: 474, width: 92, height: 74))
imgImage1.canDrawSubviewsIntoLayer = true
imgImage1.imageScaling = .scaleProportionallyDown
imgImage1.animates = true
imgImage1.image = NSImage(named: "mygif")
self.view.addSubview(imgImage1)
}
Enjoy :)
Xcode 12
Just add your gif file in your project (Not in Assets)
Add an NSImageView in your view controller.
Set the image name in the storyboard as your gif file name.
Tap on Animates property.
Answering my own question.
I was able to make a gif display in my app when I get the gif from a URL e.g.
imageView.image = NSImage(byReferencingURL: yourgifurl)
So, I figured there was something wrong with the way I copied the image into my project. Instead of putting the image in Assets.xcassets, I put it with the rest of source code and the gif shows up :) (but the animation speed seems to be very slow)
Suppose you have an animated gif resource named universe in an Assets.xcassets and NSImageView connected by IBOutlet named target:
...
#IBOutlet weak var target: NSImageView!
...
func applicationDidFinishLaunching(_ aNotification: Notification) {
...
target.animates = true
target.image = NSImage(data: NSDataAsset(name: "universe")!.data)
...
}
NOTE:
target.image = NSImage(named: "universe") // WILL NOT WORK!
I have tried enough but i don't understand what is wrong with code, it was working fine when I created an outlet, but then I found out that i don't need an outlet, so want to create it just programmatically.
var BestPractices = UIScrollView(frame: CGRectMake(0, 94, 768, 924))
BestPractices.hidden = true
I cannot access the properties of "BestPractices" in viewController , while same code works fine in playground
This is an answer from this question.
.
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)
}
add this line to identify scroll bar at last of loadview().
scrollView.backgroundColor = UIColor.blue