I'm adding a "Remove" button in a datatable to remove row. It works in a shiny app but not in a shiny module. I am pretty sure it's a namespace problem, somewhere in the shinyInput function or getRemoveButton function but I do not now how to fix it.
library(DT)
library(dplyr)
library(purrr)
library(shiny)
getRemoveButton <- function(name, idS = "", lab = "Pit") {
if (stringr::str_length(idS) > 0) idS <- paste0(idS, "-")
ret <- shinyInput(actionButton, name,
'button_', label = "Remove",
onclick = sprintf('Shiny.onInputChange(\"%sremove_button_%s\", this.id)' ,idS, lab))
return(ret)
}
shinyInput <- function(FUN, name, id, ses, ...) {
n <- length(name)
inputs <- character(n)
for (i in seq_len(n)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
uploadFigUI <- function(id) {
ns <- NS(id)
tagList(
fluidPage(
titlePanel("Uploading Files"),
sidebarLayout(
sidebarPanel(
fileInput(inputId = ns('files'),
label = 'Select an Image',
multiple = TRUE,
accept=c('image/png', 'image/jpeg')),
DT::dataTableOutput(ns("myTable"))
),
mainPanel(
uiOutput(ns('images'))
)
)
)
)
}
uploadFig <- function(input, output, session) {
ns <- session$ns
files <- eventReactive(input$files, {
req(input$files)
files <- input$files
files$datapath <- gsub("\\\\", "/", files$datapath)
files
})
values <- reactiveValues()
observeEvent(files(), {
if(is.null(values$tab)){
values$tab <- files() %>%
mutate(Remove = getRemoveButton(files()$name, idS = "", lab = "Tab1"))
}else{
tab <- files() %>%
mutate(Remove = getRemoveButton(files()$name, idS = "", lab = "Tab1"))
myTable <- bind_rows(values$tab,tab)
replaceData(proxyTable, myTable, resetPaging = FALSE)
values$tab <- myTable
}
})
output$images <- renderUI({
req(values$tab$datapath)
image_output_list <-
lapply(1:nrow(values$tab),
function(i)
{
imagename = ns(paste0("image", i))
uiOutput(imagename)
})
do.call(tagList, image_output_list)
})
observe({
req(values$tab$datapath)
for (i in 1:nrow(values$tab))
{
print(i)
local({
my_i <- i
imagename = paste0("image", my_i)
img <- knitr::image_uri(values$tab$datapath[my_i])
values$img[[i]] <- img
output[[imagename]] <- renderUI({
req(values$img[[i]])
tags$img(src = img, width = "100%", height= "auto")
})
})
}
})
proxyTable <- DT::dataTableProxy("tab")
output$myTable <- DT::renderDataTable({
req(values$tab)
DT::datatable(values$tab %>%
select(-datapath),
options = list(pageLength = 25,
dom = "rt"),
rownames = FALSE,
escape = FALSE,
editable = FALSE)
})
observeEvent(input$remove_button_Tab1, {
myTable <- values$tab
values$what <- str_replace(input$remove_button_Tab1, "button_", "")
s <- str_replace(input$remove_button_Tab1, "button_", "") %>% as.numeric()
myTable <- myTable[-s, ]
replaceData(proxyTable, myTable, resetPaging = FALSE)
values$tab <- myTable
})
}
shinyApp(
ui = fluidPage(
uploadFigUI("test")
),
server = function(input, output) {
suppfigure <- callModule(uploadFig, "test")
}
)
If this "Remove" button works, it should delete the row and the picture on the main panel.
First problem: the proxy should be proxyTable <- DT::dataTableProxy("myTable").
Second problem: you must prefix the id of the remove button with the namespace:
getRemoveButton(files()$name, idS = "test", lab = "Tab1")
This works like this.
Related
This is what I'm trying to achieve:
So I have created 2 rounded buttons in a row and gave different background colors depending whether they're selected or not. The goal is to create a kind of an illusion of a tab/toggle.
The unselected button will have the same color as the row's background color. Unfortunately, since a row is a rectangle shape there comes a residue space at the corners that still shows the background color .
This is my code for the buttons
val cornerRadius = 20.dp
var selectedIndex by remember { mutableStateOf(0)}
val configuration = LocalConfiguration.current
val screenWidth = configuration.screenWidthDp.dp
val items = listOf(
OutlinedButton(onClick = { /*TODO*/ }) {
},
OutlinedButton(onClick = { /*TODO*/ }) {
})
Row(
modifier = Modifier
.padding(top = 8.dp)
.wrapContentHeight()
.width(screenWidth).background(color = Color.Gray).clip(shape = RoundedCornerShape(20.dp))
) {
// Spacer(modifier = Modifier.weight(1f))
items.forEachIndexed { index, item ->
OutlinedButton(modifier = Modifier
.wrapContentHeight()
.width(screenWidth/2),
shape = when (index) {
// left outer button
0 -> (if (selectedIndex == index) {
RoundedCornerShape(
topStart = cornerRadius,
topEnd = cornerRadius,
bottomStart = cornerRadius,
bottomEnd = cornerRadius
)
} else {
RoundedCornerShape(
topStart = cornerRadius,
topEnd = cornerRadius,
bottomStart = cornerRadius,
bottomEnd = cornerRadius
)
})
//rightouterbutton
else -> (if (selectedIndex == index) {
RoundedCornerShape(
topStart = cornerRadius,
topEnd = cornerRadius,
bottomStart = cornerRadius,
bottomEnd = cornerRadius
)
}
else{RoundedCornerShape(
topStart = 0.dp,
topEnd = cornerRadius,
bottomStart = 0.dp,
bottomEnd = cornerRadius
)})
},
border = BorderStroke(
1.dp, if (selectedIndex == index) {
Color.Transparent
} else {
Color.Transparent
}
),
colors = if (selectedIndex == index) {
// colors when selected
ButtonDefaults.outlinedButtonColors(
backgroundColor = Color.Yellow,
contentColor = Color.Black
)
} else {
// colors when not selected
ButtonDefaults.outlinedButtonColors(
backgroundColor = Color.Gray,
contentColor = Color.Black
)
},
onClick = { selectedIndex = index },
) {
if (index == 0) {
Text(
text = "In progress",
color = if (selectedIndex == index) {
Color.Black
} else {
Color.DarkGray.copy(alpha = 0.9f)
},
modifier = Modifier.padding(horizontal = 8.dp)
)
} else {
Text(
text = "Completed",
color = if (selectedIndex == index) {
MaterialTheme.colors.primary
} else {
Color.DarkGray.copy(alpha = 0.9f)
},
modifier = Modifier.padding(horizontal = 8.dp)
)
}
}
}
}
Modifier.clip applied after the Modifier.background has no effect in your case, you need to reverse the order. Read more about why the order of modifiers matters in this answer
.clip(shape = RoundedCornerShape(20.dp))
.background(color = Color.Gray)
Another option in the case of Modifier.background is that you can apply the shape specifically to the background color. Note that this solution will not clip other view content to the shape as Modifier.clip does, but in your case it fits.
.background(color = Color.Gray, shape = RoundedCornerShape(20.dp))
I got the result for std::result::Result<Row, sqlx::Error>, I want to check the row is found.
the right code:
let sql = "select id,name from tablename LIMIT 0";
let r = sqlx::query_as::<_, Person>(sql).fetch_one(&pool).await;
if let Err(err) = r {
match err {
sqlx::Error::RowNotFound => println!("Not Found!!!"),
_ => (),
}
}
right way 1:
if let Err(err) = r {
if let sqlx::Error::RowNotFound = err {
println!("Not Found!!!");
}
}
right way 2:
r.map_err(|err| if let sqlx::Error::RowNotFound = err {
println!("Not Found!!!");
});
has the more simplify way?
You can also match like this:
match r {
Err(sqlx::Error::RowNotFound) => println!("Not Found!!!"),
_ => (),
}
You could also look at match guards
Just starting to look at both Go and Fyne. After writing a test CRUD program using information obtained from examples, I need to solve the following:
I need an initial Dialog or similar in order to allow selection of an existing record or "new" to indicate a new record. My existing Dialog does not handle this adequately.
I need to be able to enable/disable the Submit button to allow or disallow update if the data has has-not changed.
The program compiles without error, however "go vet" and VsCode indicates (eg. for line):
{"Given Names:", arwWidgets[I_NDX_GIVEN]},
go vet shows:
ERROR: "unknown field 'Key' in struct literal of type widget.FormItem",
however, if I include a key, compiler fails.
Being new to both Go and Fyne, there are probably fundamental mistakes.
Test program is as follows:
package main
// Dependency: local directory "data".
import (
"errors"
"io/ioutil"
"log"
"os"
"strings"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/dialog"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
)
const BY_SEP byte = byte('|')
const I_ARRAY_LEN int = 10
const I_NDX_GIVEN int = 0
const I_NDX_FAMILY int = 1
const I_NDX_TITLE int = 2
const I_NDX_ADDR1 int = 3
const I_NDX_ADDR2 int = 4
const I_NDX_ADDR3 int = 5
const I_NDX_STATE int = 6
const I_NDX_ZIP int = 7
const I_NDX_TELE1 int = 8
const I_NDX_TELE2 int = 9
var _arsOldData [I_ARRAY_LEN]string
var _arsNewData [I_ARRAY_LEN]string
var _tfDataHasChanged bool
var _sKey string
var _arwEntryWidgets [I_ARRAY_LEN]*widget.Entry
var _wApp fyne.App
var _wWindow fyne.Window
var _wModal widget.PopUp
var _wFormKeyEntry widget.Form
//-------------------------------------------------------------------------------
func main() {
log.Println("In fytest01")
_, err := os.Stat("./data/")
if err != nil {
log.Println("You need to create directory: 'data'")
os.Exit(1)
}
//fnReadData()
_wApp := app.New()
_wWindow := _wApp.NewWindow("Customer Details")
//_wFormKeyEntry := fnCreateKeyForm()
wFormMain := fnCreateMainForm()
//_wModal := widget.NewModalPopUp(_wFormKeyEntry, _wWindow.Canvas())
_wWindow.Resize(fyne.NewSize(500, 400))
_wWindow.CenterOnScreen()
_wWindow.SetContent(widget.NewVBox(
wFormMain,
widget.NewGroup("",
fyne.NewContainerWithLayout(layout.NewGridLayout(2),
widget.NewButton("Exit", func() {
_wApp.Quit()
}),
widget.NewButton("Submit", func() {
log.Println("Submit button pressed")
err := fnUpdateData()
if err == nil {
dialog.ShowInformation("Information", "Data was updated", _wWindow)
wEntryForKey := widget.NewEntry()
wEntryForKey.SetPlaceHolder("Enter Key (4) : ")
wEntryForKey.OnChanged = func(sKey string) {
log.Println("Entered", sKey)
if len(sKey) == 4 {
_sKey = sKey
}
if len(_sKey) == 4 {
dialog.ShowInformation("Information", "Key "+_sKey+" has been entered - press OK", _wWindow)
fnReadData()
fnRefreshWidgetData()
}
}
dialog.ShowCustom("Enter Customer Key", "OK", wEntryForKey, _wWindow)
} else {
dialog.ShowError(err, _wWindow)
}
}),
))),
)
//fnShowKeyEntryDialog()
wEntryForKey := widget.NewEntry()
wEntryForKey.SetPlaceHolder("Enter Key (4) : ")
wEntryForKey.OnChanged = func(sKey string) {
log.Println("Entered", sKey)
if len(sKey) == 4 {
_sKey = sKey
}
if len(_sKey) == 4 {
dialog.ShowInformation("Information", "Key "+_sKey+" has been entered - press OK", _wWindow)
fnReadData()
fnRefreshWidgetData()
}
}
dialog.ShowCustom("Enter Customer Key", "OK", wEntryForKey, _wWindow)
_wWindow.ShowAndRun()
//_wModal.Show()
}
//-------------------------------------------------------------------------------
func fnCreateAllEntryWidgets() {
_arwEntryWidgets[I_NDX_GIVEN] = fnCreateSingleEntryWidget(I_NDX_GIVEN)
_arwEntryWidgets[I_NDX_FAMILY] = fnCreateSingleEntryWidget(I_NDX_FAMILY)
_arwEntryWidgets[I_NDX_TITLE] = fnCreateSingleEntryWidget(I_NDX_TITLE)
_arwEntryWidgets[I_NDX_ADDR1] = fnCreateSingleEntryWidget(I_NDX_ADDR1)
_arwEntryWidgets[I_NDX_ADDR2] = fnCreateSingleEntryWidget(I_NDX_ADDR2)
_arwEntryWidgets[I_NDX_ADDR3] = fnCreateSingleEntryWidget(I_NDX_ADDR3)
_arwEntryWidgets[I_NDX_STATE] = fnCreateSingleEntryWidget(I_NDX_STATE)
_arwEntryWidgets[I_NDX_ZIP] = fnCreateSingleEntryWidget(I_NDX_ZIP)
_arwEntryWidgets[I_NDX_TELE1] = fnCreateSingleEntryWidget(I_NDX_TELE1)
_arwEntryWidgets[I_NDX_TELE2] = fnCreateSingleEntryWidget(I_NDX_TELE2)
}
//-------------------------------------------------------------------------------
func fnCreateSingleEntryWidget(iNdxData int) *widget.Entry {
wEntry := widget.NewEntry()
wEntry.SetText(_arsOldData[iNdxData])
wEntry.OnChanged = func(sText string) {
_arsNewData[iNdxData] = sText
fnCheckIfDataHasChanged()
}
return wEntry
}
//-------------------------------------------------------------------------------
func fnCreateFormFields(arwWidgets [I_ARRAY_LEN]*widget.Entry) []*widget.FormItem {
return []*widget.FormItem{
{"Given Names:", arwWidgets[I_NDX_GIVEN]},
{"Family Name: ", arwWidgets[I_NDX_FAMILY]},
{"Title: ", arwWidgets[I_NDX_TITLE]},
{"Address Ln 1: ", arwWidgets[I_NDX_ADDR1]},
{" '' Ln 2: ", arwWidgets[I_NDX_ADDR2]},
{" '' Ln 3: ", arwWidgets[I_NDX_ADDR3]},
{" '' State ", arwWidgets[I_NDX_STATE]},
{" '' Zip: ", arwWidgets[I_NDX_ZIP]},
{"Telephone 1: ", arwWidgets[I_NDX_TELE1]},
{"Telephone 2: ", arwWidgets[I_NDX_TELE2]},
}
}
//-------------------------------------------------------------------------------
func fnCheckIfDataHasChanged() {
var tfChanged bool = false
for iNdxData := 0; !tfChanged && iNdxData < len(_arsOldData); iNdxData++ {
tfChanged = (_arsNewData[iNdxData] != _arsOldData[iNdxData])
}
if tfChanged != _tfDataHasChanged {
_tfDataHasChanged = tfChanged
if tfChanged {
// COULD NOT CREATE _wBtnSubmitMain AS A GLOBAL VARIABLE.
//_wBtnSubmitMain.Show()
//_wBtnSubmitMain.Enable()
} else {
//_wBtnSubmitMain.Disable()
//_wBtnSubmitMain.Hide()
}
}
}
//-------------------------------------------------------------------------------
func fnReadData() {
_tfDataHasChanged = false
log.Println("fnReadData: Key = " + _sKey)
var sData string
if len(_sKey) > 0 {
arbData, _ := ioutil.ReadFile("./data/" + _sKey)
if arbData != nil {
sData = string(arbData)
}
}
log.Println("fnReadData: sData = " + sData)
/* CLEAR OLD DATA */
for iNdxData := 0; iNdxData < I_ARRAY_LEN; iNdxData++ {
_arsOldData[iNdxData] = ""
}
/* POPULATE DATA IF ANY */
var iNdx1 int = 0
var iNdxData int = 0
var iLen int = len(sData)
for iNdx2 := 0; iNdx2 < len(sData); iNdx2++ {
if sData[iNdx2] == BY_SEP {
_arsOldData[iNdxData] = sData[iNdx1:iNdx2]
iNdx1 = iNdx2 + 1
iNdxData++
} else if iNdx2 == (iLen - 1) {
_arsOldData[iNdxData] = sData[iNdx1 : iNdx2+1]
}
}
for iNdx := 0; iNdx < I_ARRAY_LEN; iNdx++ {
_arsNewData[iNdx] = _arsOldData[iNdx]
}
}
//-------------------------------------------------------------------------------
func fnUpdateData() error {
if !_tfDataHasChanged {
return errors.New("Data has not changed")
}
if len(_sKey) < 1 {
_sKey = "9999"
//dialog.ShowInformation("Information", "Default key of 9999 was used", _wWindow) // CAUSES ERROR
}
var sNewData string = ""
for iNdxData, sVal := range _arsNewData {
if strings.Index(sVal, "|") >= 0 {
sVal = strings.ReplaceAll(sVal, "|", ":")
}
if iNdxData != I_ARRAY_LEN-1 {
sNewData += sVal + string(BY_SEP)
} else {
sNewData += sVal
}
}
log.Println("New Data = " + sNewData)
var err error = ioutil.WriteFile("./data/"+_sKey, []byte(sNewData), 0644)
if err == nil {
for iNdxData := 0; iNdxData < len(_arsNewData); iNdxData++ {
_arsOldData[iNdxData] = _arsNewData[iNdxData]
}
fnCheckIfDataHasChanged()
}
return err
}
//-------------------------------------------------------------------------------
func fnCreateKeyForm() *widget.Form {
wEntryKey := widget.NewEntry()
return &widget.Form{
Items: []*widget.FormItem{
{"Key:", wEntryKey}},
OnSubmit: func() {
_sKey = wEntryKey.Text
log.Println("Key = " + _sKey)
fnReadData()
log.Println("Data has been read")
fnRefreshWidgetData()
_wFormKeyEntry.Hide()
//_wModal.Hide()
},
}
}
//-------------------------------------------------------------------------------
//func fnCreateMainWindow() {
// log.Println("Creating Main Window Form")
// wFormMain := fnCreateMainForm()
// log.Println("Creating Main Window Window")
// _wWindow.SetContent( //widget.NewVBox(
// wFormMain,
// )
//}
//-------------------------------------------------------------------------------
func fnCreateMainForm() *widget.Form {
log.Println("Create form1 widgets")
fnCreateAllEntryWidgets()
log.Println("Create form1 FormFields")
arwFormFields := fnCreateFormFields(_arwEntryWidgets)
log.Println("Creating Form1")
return &widget.Form{
Items: arwFormFields,
}
}
//-------------------------------------------------------------------------------
func fnRefreshWidgetData() {
for iNdx := 0; iNdx < I_ARRAY_LEN; iNdx++ {
_arwEntryWidgets[iNdx].SetText(_arsNewData[iNdx])
}
}
Can you just add a "New" button to the start window which fills in defaults then opens the main window. Does that make sense?
You create the Submit button by creating and passing it directly to SetContent. You need to save it in a variable, so that you can at some point call Disable() on it.
submitButton := widget.NewButton("Submit", func() {
log.Println("Submit button pressed")
...
})
_wWindow.SetContent(widget.NewVBox(
wFormMain,
widget.NewGroup("",
fyne.NewContainerWithLayout(layout.NewGridLayout(2),
widget.NewButton("Exit", func() {
_wApp.Quit()
}),
submitButton,
))),
)
...
if dataChanged {
submitButton.Enable()
} else {
submitButton.Disable()
}
I think the go vet message is saying you should name all the fields in your struct literal (though I get a different message to you using Go 1.13). Go allows you to add the values by order or by name - go vet is telling you that by name is safer (eg if fields are moved or inserted to the FormItem struct then your code would be broken).
Ie:
return []*widget.FormItem{
{ Text: "Given Names:", Widget: arwWidgets[I_NDX_GIVEN]},
...
Also it may simplify things to put all your global variables into a struct and then you can have "methods" on the struct. (There are a few reasons to avoid globals which you probably already know. I appreciate that this probably started as a simple test that has grown but it may be a good time to refactor.)
To change button options on toggle you need to use something like this:
buttonTitle := "Disable"
button := widget.NewButton(buttonTitle, nil)
changeButton := func() {
// here could be your logic
// how to disable/enable button
if button.Text == "Disable" {
buttonTitle = "Enable"
//button.Disable()
}
button.SetText(buttonTitle)
button.Refresh()
}
button.OnTapped = changeButton
I would like to embed numericInput and checkBoxInput in a data table. I have an example from Xie Yihui that works perfectly fine. Exactly like what I wanted.
But when the code is wrapped in a module, the edited table does not update. I read that this could be due to namespace but I do not know how to fix it.
Here are the Gists:
Gist: edit DataTable in Shiny App
Gist: edit DataTable in Shiny Module
Thanks in advance :)
There was just one little fix, which is of course hard to see if you're not familiar with Shiny modules. Every Input ID you create in the Module Server function has to be wrapped in ns (as you do in the UI), but this function is hidden in session$ns in the server function. So in line 18, where the inputs are created, their ID had to be adjusted using session$ns. From
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
to
inputs[i] = as.character(FUN(paste0(session$ns(id), i), label = NULL, ...))
Full code:
library(shiny)
library(DT)
editTableUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns('x1')),
verbatimTextOutput(ns('x2'))
)
}
editTable <- function(input, output, session) {
# create a character vector of shiny inputs
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(session$ns(id), i), label = NULL, ...))
}
inputs
}
# obtain the values of inputs
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
# a sample data frame
res = data.frame(
v1 = shinyInput(numericInput, 100, 'v1_', value = 0),
v2 = shinyInput(checkboxInput, 100, 'v2_', value = TRUE),
v3 = rnorm(100),
v4 = sample(LETTERS, 100, TRUE),
stringsAsFactors = FALSE
)
# render the table containing shiny inputs
output$x1 = DT::renderDataTable(
res, server = FALSE, escape = FALSE, options = list(
preDrawCallback = JS('function() {
Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() {
Shiny.bindAll(this.api().table().node()); } ')
)
)
# print the values of inputs
output$x2 = renderPrint({
data.frame(v1 = shinyValue('v1_', 100), v2 = shinyValue('v2_', 100))
})
}
shinyApp(
ui = fluidPage(
editTableUI("test")
),
server = function(input, output) {
callModule(editTable, "test")
}
)
I'm new in swift and SQLLite , I had a problem and I didn't know how to fix it
I create table contain [id name Grade] and this is my insert function
let insertStatementString = "INSERT INTO Contact (Id, Name, Grade ) VALUES (?, ?, ?);"
func insert() {
var insertStatement: COpaquePointer = nil
// 1
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
let dic: [NSString] = ["Ray", "Chris", "Martha", "Danielle"]
let grade = [11 , 13 ,11 ,12]
var id = Int32()
for (index, name) in dic.enumerate() {
sqlite3_bind_int(insertStatement, 1, index + 1)
sqlite3_bind_text(insertStatement, 2, name.UTF8String, -1, nil)
sqlite3_bind_int(insertStatement, 3,Int32(grade[index]))
id = id + 1
}
if sqlite3_step(insertStatement) == SQLITE_DONE {
print("Successfully inserted row.")
} else {
print("Could not insert row.")
}
} else {
print("INSERT statement could not be prepared.")
}
// 5
sqlite3_finalize(insertStatement)
}
insert()
when i call my query function , the while loop work for one time only , but as you can see i had 4 elements in my table
this is my query function
let queryStatementString = "SELECT * FROM Contact;"
func query() {
var queryStatement: COpaquePointer = nil
if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
while (sqlite3_step(queryStatement) == SQLITE_ROW) {
let id = sqlite3_column_int(queryStatement, 0)
let queryResultCol1 = sqlite3_column_text(queryStatement, 1)
let grade = sqlite3_column_int(queryStatement, 2)
let name = String.fromCString(UnsafePointer<CChar>(queryResultCol1))!
print("Query Result:")
print("\(id) | \(name) | \(grade)")
}
} else {
print("SELECT statement could not be prepared")
}
sqlite3_finalize(queryStatement)
}
query()
last thing , this my create Table function
let db = openDatabase()
let createTableString = "CREATE TABLE Contact(" + "Id INT PRIMARY KEY NOT NULL," + "Name CHAR(255)," + "Grade INTEGER)"
func createTable() {
var createTableStatement: COpaquePointer = nil
if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
if sqlite3_step(createTableStatement) == SQLITE_DONE {
print("Contact table created.")
} else {
print("Contact table could not be created.")
}
} else {
print("CREATE TABLE statement could not be prepared.")
}
sqlite3_finalize(createTableStatement)
}
createTable()
In your insert() function, you call sqlite3_step() once. This is why you insert a single row. Move the call to sqlite3_step() inside your loop on names and grades. Also call sqlite3_reset() before setting bindings.