unresolved identifier in swift 3 - uibutton

My code is trying to download some JSON data and save it to an array, then loop through the array and create a button for each item. I am having trouble assigning my function to the buttons for giving them functionality. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//connect to website
let SongArray: Array<Any>
let url = URL(string:"*******")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
//download JSON data from php page, display data
let SongArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [[String]]
print(SongArray)
//Make buttons with JSON array
var buttonY: CGFloat = 20
for song in SongArray {
let SongButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // 50px spacing
SongButton.layer.cornerRadius = 10 //Edge formatting for buttons
SongButton.backgroundColor = UIColor.darkGray //Color for buttons
SongButton.setTitle("\(song[0])", for: UIControlState.normal) //button title
SongButton.titleLabel?.text = "\(song[0])"
SongButton.addTarget(self,action: #selector(songButtonPressed(_:)), for: UIControlEvents.touchUpInside) //button press / response
self.view.addSubview(SongButton) // adds buttons to view
}
}
catch
{
}
}
}
}
task.resume()
}
} //close viewDidLoad
func songButtonPressed(_sender:UIButton!) { // function for buttons
if sender.titleLabel?.text == "\("Song[0]")" {
print("So far so good!!")
}
}
I am getting an error on the line with SongButton.addTarget...
the error says 'Use of Unresolved Identifier "SongButtonPressed"' even though its declared right after the viewDidLoad function.

Since you declared the selector SongButtonPressed(_:) it's (note the underscore)
func SongButtonPressed(_ sender: UIButton) { // no implicit unwrapped optional !!
By the way the proper selector syntax is #selector(SongButtonPressed(_:))
Two notes:
.mutableContainers has no effect in Swift at all. Omit the parameter. And delete the line let SongArray: Array<Any>. You should get an unused warning.
Functions, methods and variables are supposed to start with a lowercase letter.

Related

Unable to reload/redisplay PDF in WkWebView; but RTF apparently works

Scenario: TabbedUI with Tab #1 being a WkWebView displaying a PDF of general information (Info).
Problem: When I exit tab #1 (info) for another tab (ex. search), and return....I get an empty PDF with the following error message in the console:
Could not signal service com.apple.WebKit.WebContent: 113: Could not
find specified service
However this doesn't happen when I use a standard .rtf (Rich Text Format) file.
Here's my code:
import SwiftUI
import WebKit
struct IntroSwiftUI: View {
var body: some View {
Webview()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.bottom)
}
}
// =====================================================================================================
struct Webview: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
guard let url = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") else {
print("Inside Webview: unable to reload ReadMe.")
return WKWebView()
}
let request = URLRequest(url: url)
let wkWebview = WKWebView()
wkWebview.load(request)
wkWebview.scrollView.bounces = false
return wkWebview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
I just want a static PDF to be available for viewing whenever the user which to read some documentation (with embedded images). But apparently it tries to rebuild and gets lost.
Do I need to do something 'special' with the PDF, like release it when exiting & re-create it upon return? That seems totally inefficient.
Per suggestion,
Use the PDFView paradigm.
Here is my revised (correct) code for PDF viewing:
import SwiftUI
import UIKit
import PDFKit
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
// =====================================================================================================
struct IntroSwiftUI: View {
var body: some View {
if let documentURL = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") {
VStack(alignment: .center) {
Text("Introduction")
.font(.title3)
.fontWeight(Font.Weight.medium)
PDFKitView(url: documentURL)
}
} else {
Text("Sorry, No PDF")
}
}
}

NSApp: Hide main window until proper ViewController can be loaded

I have a MacOS app that is a registered custom URL handler.
I'm trying to make it show a specific NSViewController if the app is started via the url handler or the regular window and ViewController if no parameters are used.
In my AppDelegate.swift I have this:
func application(_ application: NSApplication, open urls: [URL]) {
AppDelegate.externalCaller = true;
NSApp.hide(self)
let url: URL = urls[0];
// Process the URL.
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true);
let method = components?.host;
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if (method == "DO_STH") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "DO_STH_2") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "PROCESS_STUFF") {
// Show window
DispatchQueue.main.async {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let restoringViewController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("restoringData")) as! RestoringViewController;
if let window = NSApp.mainWindow {
window.contentViewController = restoringViewController;
}
NSApp.activate(ignoringOtherApps: true);
AppDelegate.restoreData();
}
}
}
}
}
The problem is that by the time NSApp.hide(self) runs, there's already a window visible with the default viewController, creating some flickering effect.
What's the best way to make the app start without any visible window by default and only show the window if it wasn't started with any URL parameter and later on demand if a specific URL parameter exists?
Unchecking "Is initial Controller" and adding this to AppDelegate solved my issue
func applicationDidFinishLaunching(_ notification: Notification) {
if (!AppDelegate.externalCaller) {
showWindow();
}
}
func showWindow() {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let windowController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MainWindowController")) as! NSWindowController;
windowController.showWindow(self);
}

How to disable ScrollView Bounce In SwiftUI

Any Modifier available to stop bounce of ScrollView in swiftUI ?
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
I tried below code but it not work for me. looks like it deprecated
ScrollView(alwaysBounceVertical: true) {
Image("test")
Text("Hello Text")
...
...
}
try using this line of code:
UIScrollView.appearance().bounces = false
You can use it like this:-
struct RoomDetailsView: View {
init() {
UIScrollView.appearance().bounces = false
}
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
}
}
Or you can write this line in AppDelegate to apply this behaviour throughout into your app.
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UIScrollView.appearance().bounces = false
}
You may use SwiftUI-Introspect library:
ScrollView {
// some content
}
.introspectScrollView { scrollView in
scrollView.alwaysBounceVertical = false
}
Set the ScrollView's showsIndicators parameter equal to false so the user interacting with your view doesn't activate the scroll indicator (it can happen even without scrolling enabled).
ScrollView(showsIndicators: false)
In the onAppear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = false
In the onDisappear modifier for the ScrollView, add this line.
UIScrollView.appearance().bounces = true
If you set UIScrollView.appearance().bounces equal to false in the init, it will prevent all of your app's ScrollViews from bouncing. By setting it equal to true in the onAppear modifier and equal to false in the onDisappear modifier, you ensure that it only effects the one ScrollView.
Your ScrollView should look something like this.
ScrollView(showsIndicators: false) {
...
...
...
}
.onAppear {
UIScrollView.appearance().bounces = false
}
.onDisappear {
UIScrollView.appearance().bounces = true
}
A better solution would be to use viewbuilder and create your own scrollview that doesn't bounce when the content size is less than scrollview frame size.
import SwiftUI
struct BounceVerticalDisableScrollView<Content: View>: View {
#State private var alwaysBounceVertical = false
let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
GeometryReader { scrlViewGeometry in
ScrollView {
content
.background(
GeometryReader {
// calculate height by clear background
Color.clear.preference(key: SizePreferenceKey.self,
value: $0.frame(in: .local).size.height)
}.onPreferenceChange(SizePreferenceKey.self) {
self.alwaysBounceVertical = $0 < scrlViewGeometry.size.height
}
)
}
// disable scroll when content size is less than frame of scrollview
.disabled(self.alwaysBounceVertical)
}
}
}
// return size
public struct SizePreferenceKey: PreferenceKey {
public static var defaultValue: CGFloat = .zero
public static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
So, that you can use the scrollview as:
var body: some View {
VStack (alignment: .leading, spacing: 10) {
BounceVerticalDisableScrollView{
VStack (alignment: .leading, spacing: 10) {
....
....
}.padding()
}
}
}
Apple introduced an new modifier named scrollBounceBehavior with iOS 16.4 that can be used to prevent the ScrollView from bouncing when the content is smaller than the screen.
https://developer.apple.com/documentation/swiftui/view/scrollbouncebehavior(_:axes:)
struct RoomDetailsView: View {
var body: some View {
ScrollView(showsIndicators: false) {
Image("test")
Text("Hello Text")
...
...
}
.scrollBounceBehavior(.basedOnSize)
}
}

How can I present an overlay above the keyboard in SwiftUI?

I have a screen in my app that automatically displays a keyboard when the screen is presented. The screen allows users to enter their mobile number. In order to select a dialling code, the user needs to tap a button which will then trigger the presentation of an overlay.
Problem
The overlay is being presented, but it's showing up behind the currently present keyboard.
Question
How can I make this overlay be the very top view?
There is no way for me to use the zIndex modifier on the keyboard for obvious reasons. I'm wondering if there is a way to make the overlay the top view when it's about to be presented, or if the overlay can be added to the window.
Thanks in advance
You should probably only have one source of input at any given time — either the keyboard should be presented to enter a number, or the overlay should be presented to pick the dialing code, not both. Here's an example which hides the keyboard when overlay appears, and vice versa. (Keyboard dismissal code from this answer.)
struct ContentView: View {
#State private var number = ""
#State private var showingOverlay = false
var body: some View {
GeometryReader { proxy in
ZStack(alignment: .top) {
// Just here to force ZStack to use the whole screen
Rectangle()
.fill(Color.clear)
VStack(alignment: .leading) {
Button("Select Dialing Code") {
UIApplication.shared.endEditing()
self.showingOverlay = true
}
TextField("Enter your number", text: self.$number, onEditingChanged: {
if $0 { self.showingOverlay = false }
})
.keyboardType(.phonePad)
}
Overlay(showing: self.$showingOverlay)
.frame(height: 400)
.offset(x: 0, y: proxy.size.height + (self.showingOverlay ? -300 : 100))
.animation(.easeInOut)
}
}
}
}
struct Overlay: View {
#Binding var showing: Bool
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.fill(Color(.systemGray4))
Button("Dismiss") {
self.showing = false
}
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}

Load more functionality using SwiftUI

i have used ScrollView with HStack, now i need to load more data when user reached scrolling at last.
var items: [Landmark]
i have used array of items which i am appeding in HStack using ForEach
ScrollView(showsHorizontalIndicator: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
What is the best possible solution to manage load more in SwiftUI without using custom action like loadmore button.
It's better to use ForEach and List for this purpose
struct ContentView : View {
#State var textfieldText: String = "String "
private let chunkSize = 10
#State var range: Range<Int> = 0..<1
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("Load more")
}
}
}
func loadMore() {
print("Load more...")
self.range = 0..<self.range.upperBound + self.chunkSize
}
}
In this example each time you press load more it increases range of State property. The same you can do for BindableObject.
If you want to do it automatically probably you should read about PullDownButton(I'm not sure if it works for PullUp)
UPD:
As an option you can download new items by using onAppear modifier on the last cell(it is a static button in this example)
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("")
}
.onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 10)) {
self.loadMore()
}
}
}
}
Keep in mind, that dispatch is necessary, because without it you will have an error saying "Updating table view while table view is updating). Possible you may using another async way to update the data
If you want to keep using List with Data instead of Range, you could implement the next script:
struct ContentView: View {
#State var items: [Landmark]
var body: some View {
List {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
.onAppear {
checkForMore(landmark)
}
}
}
}
func checkForMore(_ item: LandMark) {
guard let item = item else { return }
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
// function to request more data
getMoreLandMarks()
}
}
}
Probably you should work in a ViewModel and separate the logic from the UI.
Credits to Donny Wals: Complete example